Guide · System design
Leader Election, Leases and Distributed Locks
26 min readUpdated By Saket Bhatnagar
Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.
In short
- A distributed lock is a shared flag that lets only one process, out of many running on different servers, do a piece of work at a time.
- Almost every practical distributed lock is a lease: a lock that ends on its own after a set time, so a crashed holder cannot block everyone forever.
- A lease can end while its holder is only frozen, for example by a long garbage-collection pause (the runtime stopping a program to free memory). Then two processes both believe they hold the lock.
- A fencing token fixes the damage. The lock service hands out a number that only goes up, and the database rejects any write carrying an older number.
- Beginners can stop after the section on choosing a lock service. Engineers then get our test of freezing a lock holder past its lease, where the fence turned away the woken holder's first stale write in 6 of 6 runs. They also get three gaps fencing leaves open.
What goes wrong when a scheduled job runs on more than one server?
The job runs once on every server. Each copy of your app has the same timer, and nothing tells the copies that one of them already started.
Picture a common setup. A company sells warehouse software, and its app runs on three servers so one crash cannot take it down. The setup is made up, but the problem is real.
Every night at 2 a.m., two jobs run. A stock job matches the day's barcode scans against stock records and rebuilds a report. An invoice job bills each of about 600 clients and queues an email.
The team copied the same setup to all three servers, timer included. At the next month-end, each client got three identical invoices: 1,800 emails instead of 600 (made up).
Nightly jobs before any lock
Drag to exploreNotice that nothing sits between the timer and the database: three servers, three full runs, three invoices per client.
Diagram connections:
- 2 a.m. timer to App server 1
- 2 a.m. timer to App server 2
- 2 a.m. timer to App server 3
- App server 1 to Invoices table
- App server 2 to Invoices table
- App server 3 to Invoices table
- Invoices table to Invoice emails (Mailer sends new rows)
- 2 a.m. timer: Same setting copied to every server
- App server 1: Runs the invoice job
- App server 2: Runs the invoice job too
- App server 3: And so does this one
- Invoices table: Gets every invoice three times
- Invoice emails: Mailer sends one per new row
The team needs one rule: whichever server starts the job first, the others stay out. That rule is a distributed lock, and this guide builds one, breaks it and then makes it safe.
What is a distributed lock?
A distributed lock is a flag, kept in a shared service, that lets only one process do a piece of work at a time. Every other process sees the flag and waits or skips the work.
You may know locks inside one program. A thread takes a mutex (a lock only one thread can hold), does its work and lets go. The language runtime and operating system enforce it, because all threads share one memory.
Across machines there is no shared memory. So the lock must live in a service every server can reach, such as Redis, ZooKeeper, etcd or a database.
The Redis documentation lists the promises a lock needs. Safety, or mutual exclusion, means at most one client holds the lock at any moment. Liveness means the lock can still be taken eventually, even if the holder crashes.
Those two promises pull against each other. To free the lock after a crash, the service must decide the holder is dead. From outside, a holder that is merely very slow looks exactly like a dead one.
First ask what a double run would cost
Martin Kleppmann, a researcher who writes about distributed systems, splits locks into two kinds by one question. What happens if two processes hold the lock at the same time?
- Efficiency lock. A double run only wastes work. The stock job just rebuilds a report, so a second run burns some CPU and nothing more.
- Correctness lock. A double run breaks data or hurts people. The invoice job records money owed, so a second run means duplicate bills.
An efficiency lock can be simple and occasionally wrong. A correctness lock needs an extra guard, called a fencing token, which this guide builds later.
What is a lease, and why does a distributed lock need a timeout?
A lease is a lock that ends on its own after a fixed time unless the holder renews it. The timeout frees the lock when the holder crashes, instead of blocking the job forever.
Think of a kavi sammelan, a poetry evening with many poets and one microphone. The anchor hands the mic to one poet for an eight-minute slot. When the slot ends, the anchor may call the next poet, finished or not.
Here is how the picture maps. The mic is the lock, the anchor is the lock service, the slot is the lease, and a poet asking for two more minutes is a renewal.
The anchor never needs to know whether the poet fainted, wandered off or is still mid-verse. The clock decides, and the evening keeps moving. That is liveness.
Where the picture breaks: a poet sees the anchor's signal, but a frozen server sees nothing. When it wakes, it does not know its slot has ended, and it keeps reading aloud.
So choosing the length is a trade-off. A long lease means a long wait after a real crash. A short lease means a slow holder loses the lock more often, so long jobs take a short lease and renew it.
How do you build a distributed lock with Redis?
Use one Redis command that creates a key only if it does not exist yet, with an expiry time. Whichever server creates the key holds the lock.
Redis is an in-memory data store that many teams already run as a cache. The command is SET with two options: NX means set the key only if it is new, and PX gives the expiry in milliseconds.
Each server stores a random value in the key, unique to this attempt. When it finishes, it deletes the key only if the value is still its own. Here is the team's version, using the redis-py client library.
import uuid
import redis
r = redis.Redis(host="lock-redis", port=6379)
RELEASE = """
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
end
return 0
"""
def run_nightly_invoices():
my_id = str(uuid.uuid4()) # unique to this attempt
if not r.set("lock:invoices", my_id, nx=True, px=30_000):
return # someone else holds it
try:
generate_invoices() # the real work
finally:
r.eval(RELEASE, 1, "lock:invoices", my_id)The set call with nx=True is the whole lock. In our test, three clients raced for the same key 500 times, and exactly one won every round on Redis 7.0.15.
The px value is a 30-second lease. The invoice job runs for about 40 minutes at month-end (made up), so a helper thread renews the lease every 10 seconds. It runs the same check as RELEASE, with PEXPIRE (reset the expiry) in place of DEL.
RELEASE is a Lua script, a small program that Redis runs as one step. The Redis documentation says scripts run atomically, so nobody can sneak in between the check and the delete.
You can check the lock by hand with redis-cli, the Redis command-line tool. The second SET gets (nil) because the key exists. PTTL shows the milliseconds left, just under 900000, so your number will differ slightly.
$ redis-cli SET lock:invoices test-1 NX PX 900000
OK
$ redis-cli SET lock:invoices test-2 NX PX 900000
(nil)
$ redis-cli PTTL lock:invoices
(integer) 899927
$ redis-cli DEL lock:invoices
(integer) 1Newer Redis has a shortcut. The Redis documentation says DELEX, added in Redis Open Source 8.4.0, deletes a key only if its value matches. So DELEX key IFEQ value replaces the script (as of October 2026).
What a Redis replica does to your lock
Many setups add a replica, a copy that takes over if the main node dies. Redis replication is asynchronous by default, so the replica receives each write a moment later, not at once.
The Redis documentation spells out the race. The main node grants a lock and crashes before the replica hears about it. The replica is promoted, and a second client takes the same lock.
Kleppmann's advice for efficiency locks is a single Redis node, accepting that it is sometimes wrong. The team's stock job fits that exactly.
Why can a distributed lock still let two workers in at once?
A lease ends by the clock, but the old holder may only be frozen and not know its time is up. When it wakes, it carries on writing as if it still held the lock.
Here is how it plays out on a month-end night (times made up). At 2:14:05 the virtual machine running server 1 is suspended by its host for 40 seconds. No code runs, so no renewal is sent.
The lease runs out about 30 seconds after the last renewal. Every server's scheduler retries the job until tonight's run is marked done. Server 2 retries, finds no lock and starts billing from client 1.
At 2:14:45 server 1 wakes. Its helper thread learns on the next renewal that the lock is gone. Nothing tells the billing loop, so both servers bill the same clients.
The obvious fix is to check the lease just before each write. Kleppmann explains why that cannot work: the pause can strike after the check and before the write, and no code runs to notice.
Long pauses are not exotic. Kleppmann notes stop-the-world garbage-collection pauses (every thread halted) have sometimes lasted several minutes, and gives other ways a process can stall:
- A page fault, where the program waits while a piece of its memory is read back from disk.
- A CPU shared with many other busy processes, so yours waits its turn.
- A network packet delayed for a long time after it was sent.
- A SIGSTOP signal, which an operator or a tool can send to freeze a process.
Clocks add a second risk. The Redis documentation says Redis does not use a monotonic clock (one that never jumps backwards or forwards) for key expiry, so a wall-clock jump can end a lease early.
What is a fencing token?
A fencing token is a number the lock service hands out with every grant, always higher than the last. The database checks it on every write and rejects any number older than the newest it has seen.
The trick is who does the checking. The frozen server cannot know it is stale, so the check moves to the database, which sees every write in order. Kleppmann notes this needs the storage to take an active role.
Back to the kavi sammelan. Now the anchor gives each poet a numbered slip with the mic, and the sound engineer ignores anyone holding a slip older than the newest one seen. The confused poet can talk, but the speakers stay off.
For the team, the invoices database plays the sound engineer. A one-row table called job_fence stores the newest token for the job. The invoices table makes client and month a primary key, a set of columns no two rows can share. So each client gets one invoice per month.
CREATE TABLE job_fence (
job text PRIMARY KEY,
last_token bigint NOT NULL
);
INSERT INTO job_fence VALUES ('invoices', 0);
CREATE TABLE invoices (
client_id int,
month text,
amount numeric(12,2),
token bigint NOT NULL,
PRIMARY KEY (client_id, month)
);Every invoice write goes through this code, written with psycopg 3, the PostgreSQL driver for Python. The token comes from the lock service when the server takes the lock.
import psycopg
class StaleLockError(Exception):
pass
FENCE = (
"UPDATE job_fence SET last_token = %s "
"WHERE job = 'invoices' AND last_token <= %s"
)
def check_fence(conn, token):
cur = conn.execute(FENCE, (token, token))
if cur.rowcount == 0:
raise StaleLockError(f"token {token} is older than the newest")
def save_invoice(conn, token, client_id, month, amount):
with conn.transaction():
check_fence(conn, token)
conn.execute(
"INSERT INTO invoices (client_id, month, amount, token) "
"VALUES (%s, %s, %s, %s) "
"ON CONFLICT (client_id, month) DO NOTHING",
(client_id, month, amount, token),
)
def run_invoice_job(conn, token, month, bills):
with conn.transaction():
check_fence(conn, token) # first step: raise the fence
for client_id, amount in bills:
save_invoice(conn, token, client_id, month, amount)check_fence raises the stored number to your token, but only if your token is at least as new. It uses at least, not greater than, because one holder writes hundreds of invoices with the same token.
The fence update and the insert share one transaction, a group of database steps that are saved together or not at all. So a rejected token never leaves an invoice behind.
ON CONFLICT DO NOTHING makes a redo harmless. If the new holder reaches a client the old holder already billed, the insert quietly does nothing instead of creating a second bill.
The frozen night with fencing tokens
Drag to exploreThe numbers come from our etcd test later on. Follow the arrows to the question box: the database compares tokens, so the woken server's write is turned away.
Diagram connections:
- Server 1 gets token 9 to Server 1 freezes 12 s
- Server 1 freezes 12 s to Lease runs out
- Lease runs out to Server 2 gets token 11
- Server 2 gets token 11 to Server 2 bills clients
- Server 2 bills clients to Server 1 wakes, sends 9
- Server 1 wakes, sends 9 to Is 9 at least 11?
- Is 9 at least 11? to Write rejected (No)
- Is 9 at least 11? to Write accepted (Yes)
- Server 1 gets token 9: Lease of 5 seconds starts
- Server 1 freezes 12 s: No renewals reach the lock service
- Lease runs out: Lock service deletes the lock key
- Server 2 gets token 11: Raises the fence to 11 first
- Server 2 bills clients: job_fence now holds 11
- Server 1 wakes, sends 9: Still thinks it holds the lock
- Is 9 at least 11?: Database compares tokens
- Write rejected: Server 1 stops its job
- Write accepted: Only a current holder gets here
Where does the token come from?
The token must come from something that never hands out a smaller number after a bigger one, even when servers fail. A counter in one Redis node can lose its newest value when a replica takes over.
Services built on consensus give you such numbers. Consensus means a group of servers agrees on every change even when a minority of them fail.
Kleppmann suggests ZooKeeper's zxid, a number stamped on every change, or a node's version. The etcd documentation says the authors treat etcd's revision number, a counter that rises with every change, as the fencing token.
Redlock, a multi-node Redis algorithm covered later, gives no fencing token. Kleppmann points out that its random value never goes up, so storage cannot tell old from new.
How does leader election work with ZooKeeper and etcd?
Leader election picks one server out of many to do a job for as long as it stays healthy. In ZooKeeper, each server creates a numbered node, and the server holding the smallest number leads.
Think of leader election as a long-lived lock. Instead of locking each job, the team wants one of the three servers to run the scheduler for every nightly job. If that server dies, another takes over.
ZooKeeper is a coordination service that keeps a small tree of data, made of nodes called znodes, on a group of servers called an ensemble. Its overview page says the service stays available while a majority of those servers are up. etcd, covered below, reaches agreement with Raft, a well-known consensus algorithm.
Two ZooKeeper features make election work. An ephemeral node exists only while the session that created it is alive. A sequential node gets a 10-digit counter added to its name, like n_0000000000.
A session is the link between one client and ZooKeeper, kept alive by small regular PING messages. If the cluster hears nothing within the session timeout, the session expires and its ephemeral nodes are deleted.
Volunteer
Each server creates an ephemeral, sequential node under the same election node. Server A gets n_0000000000, server B gets n_0000000001 and server C gets n_0000000002.
Find the smallest
Each server lists the election node's children. The one holding the smallest number, server A, becomes the leader and starts the nightly scheduler.
Watch only the one in front
Every other server sets a watch, a one-time alert, on the node just before its own. Server B watches A's node, and server C watches B's.
Take over when alerted
If server A's session expires, its node disappears and only server B is alerted. B lists the children again, sees it is now smallest, and leads.
Leader election in ZooKeeper
Drag to exploreNotice that each watch arrow points at just one other node, so a failure wakes one server, not all of them.
Diagram connections:
- Server A: n_0000000000 to ZooKeeper ensemble (Keeps session alive)
- Server B: n_0000000001 to Server A: n_0000000000 (Watches A's node)
- Server C: n_0000000002 to Server B: n_0000000001 (Watches B's node)
- Server A: n_0000000000 to Nightly scheduler (Runs as leader)
- ZooKeeper ensemble: Servers that agree on every change
- Server A: n_0000000000: Smallest number: the leader
- Server B: n_0000000001: Next in line
- Server C: n_0000000002: Third in line
- Nightly scheduler: Runs stock and invoice jobs
If every server watched the leader, one failure would wake them all at once. The ZooKeeper recipes page calls this the herd effect, and its lock recipe avoids it the same way.
The election still needs fencing. The ZooKeeper programmer's guide says the cluster, not the client, expires sessions, and a cut-off client is not told until it reconnects.
etcd is the key-value store where the Kubernetes API server keeps cluster state. Kubernetes runs containers across many servers. etcd offers the same building blocks under other names: a lease is a timer you attach keys to, and its lock is tied to a lease.
The etcd documentation is blunt about limits. It says etcd's lock by itself cannot protect resources outside etcd. Those resources must check a version themselves, which is exactly what job_fence does.
Should you use Redis, ZooKeeper or etcd for a distributed lock?
Use a single Redis lock when a rare double run only wastes work. Use ZooKeeper or etcd with fencing tokens when a double run would corrupt data. Redlock sits in between and is disputed.
Redlock is described in the Redis documentation. A client tries to take the same lock on several independent Redis nodes, five in the example. The lock counts only if a majority, at least three, agreed in time.
Kleppmann argued in 2016 that Redlock's safety depends on timing assumptions: bounded network delay, bounded pauses and bounded clock error. He advised a consensus system such as ZooKeeper for correctness.
Salvatore Sanfilippo, who designed Redlock, replied the same year with a post asking whether Redlock is safe. He argued the random value can act like a token if the storage does a check-and-set on it.
Both sides agree on one thing: the storage must check something. As of October 2026, the Redis documentation itself tells readers to implement fencing tokens, especially for work that can take a long time.
| Single Redis node | Redlock (5 Redis nodes) | ZooKeeper | etcd | |
|---|---|---|---|---|
| How you take the lock | SET with NX and an expiry | The same SET on a majority of nodes | Ephemeral sequential node | Key attached to a lease |
| What frees it if the holder dies | The key expires | The keys expire on each node | Session expires, node is deleted | Lease expires, key is deleted |
| Fencing token | None built in | None; the random value never goes up | zxid of the node, or a node's version | Revision when the lock was granted |
| Keeps working if one lock server crashes | No; with a replica, a failover can lose a lock | Yes, while a majority is up | Yes, while a majority is up | Yes, while a majority is up |
| Good fit | Efficiency locks | Disputed for correctness | Leader election and correctness locks | Correctness locks with fencing |
The team's rule fits in one small picture. Start at the top for each job.
Choosing a lock for each job
Drag to exploreThe question about harm, not speed, decides which lock a job gets.
Diagram connections:
- A job needs one runner to Would two runs corrupt data?
- Would two runs corrupt data? to Single Redis lock (No)
- Would two runs corrupt data? to ZooKeeper or etcd lock (Yes)
- ZooKeeper or etcd lock to Database checks tokens (Token on every write)
- Database checks tokens to One invoice per month (Plus)
- A job needs one runner: Nightly stock or invoice job
- Would two runs corrupt data?: Ask before picking a lock
- Single Redis lock: Stock report: a rerun wastes CPU
- ZooKeeper or etcd lock: Invoices: one runner, with a token
- Database checks tokens: Rejects writes from old holders
- One invoice per month: Primary key stops redone work
What happened when we froze a lock holder in a test?
Without fencing, the frozen server woke and kept billing: 6 more clients in the Redis run, and 7 to 10 in the etcd runs. With fencing, its very next write was rejected, in 6 of 6 etcd runs. No client was billed twice there, but the primary key on client and month would have blocked duplicate rows anyway. So the key stops duplicate rows, and the fence stops the woken server writing anything more.
We ran it in October 2026 on one machine: Redis 7.0.15 with redis-py 8.1.0, etcd 3.4.30, PostgreSQL 16.15 with psycopg 3.3.6, and Python 3.13.16. Runner 1 bills 10 clients, one every half second.
To copy a frozen virtual machine, we sent SIGSTOP to the lock holder for longer than its lease, then started a second runner. With etcd we froze both the job and etcdctl, the etcd command-line tool holding the lock.
| What we measured | Redis lock, no fencing | etcd lock, no fencing | etcd lock plus fencing |
|---|---|---|---|
| Lease and freeze | 3 s lease, renewed every 1 s; frozen about 6 s | 5 s TTL (lease length); frozen 12 s | 5 s TTL (lease length); frozen 12 s |
| Second runner took the lock | Once the 3 s lease ran out | 3.9 to 5.5 s after the freeze, in 3 runs | 4.2 to 5.9 s after the freeze, in 6 runs |
| First runner after waking | Billed clients 5 to 10, while server 2 was still billing | Billed the rest of its 10 clients | First write rejected in 6 of 6 runs, for example token 9 against 11 |
| Shared key on client and month | No | No | Yes, so a second row was impossible |
| Clients billed twice | 10 of 10 | 10 of 10, in 3 of 3 runs | 0 of 10, in 6 of 6 runs |
The etcd lease ends 5 seconds after its last renewal. That renewal came shortly before the freeze, so takeover took under 6 seconds.
In our first fenced run the tokens were 9 and 11, not 9 and 10. Revisions rise with every change in etcd, so tokens skip numbers. That is fine, because fencing only needs a bigger number, not the next one.
You can get the token yourself. etcdctl lock --ttl=5 invoices -- ./job.sh runs job.sh while holding the lock. The script sees the token in the ETCD_LOCK_REV environment variable, the etcd revision when the lock was granted. We ran 3.4.30, an older release line, and the etcdctl source for 3.7.2 sets the same variable.
Start the servers
Run a scratch Redis, etcd and PostgreSQL on your laptop. Create job_fence and invoices with the SQL above.
Start runner 1
Run the job under etcdctl lock with a short TTL, such as 5 seconds, and let it bill a few clients.
Freeze it
Send SIGSTOP to runner 1's whole process group, so the job and etcdctl both stop.
Start runner 2
It should get the lock within about one TTL of the freeze, with a higher ETCD_LOCK_REV.
Wake runner 1
Send SIGCONT. Expect its next save_invoice to raise StaleLockError. Then SELECT client_id FROM invoices GROUP BY client_id HAVING count(*) > 1 should return no rows.
What gaps does a fencing token still leave open?
Fencing only protects writes that pass through a store that checks the token. We found three gaps in testing and in the docs, and each has a simple fix.
A stale write before the new holder's first write
The database learns a new token only when the new holder first writes. Until then, the old holder's token is still the newest it has seen.
In our PostgreSQL test, token 41 wrote successfully while the next holder, token 42, had not touched the fence yet. Once 42 raised the fence, 41 was rejected.
That is why run_invoice_job calls check_fence as its very first step, before billing anyone. The gap shrinks to the moment between taking the lock and that first call.
Side effects outside the database
An email service or a payment gateway does not check your tokens. If the job sends the email itself, a frozen holder can still send one after waking.
So the team's job only writes invoice rows, and a separate mailer sends one email per new row. The fenced database decides what exists, and the email follows it. The mailer needs one runner of its own, or the double send just moves there.
Counters that restart
ZooKeeper's programmer's guide says the sequence counter is kept by the parent node. In our test on ZooKeeper 3.9.1 with kazoo 2.11.0, deleting and re-creating the election node restarted names at n_0000000000.
The node's czxid, the zxid of the change that created it, kept rising: 6, 8 and 10 before, 18 after. So use czxid as the token, or never delete the election node.
One more ZooKeeper result is worth knowing. We froze the leader for 12 seconds with a 4-second session timeout. Only the next server in line was alerted, about 4.7 seconds later.
The frozen leader heard nothing until it woke. Its client then reported the connection as suspended, and as lost about 0.1 seconds later. For that moment it still believed it led, which is the gap fencing covers.
How do you explain distributed locks in a system design interview?
Start from why only one runner matters, show why a lease alone is not enough, and finish with fencing tokens and idempotent writes, meaning writes that are safe to repeat.
Say why you need one runner
Name the job and what a double run costs. Then say whether this lock is for efficiency or for correctness.
Pick a lock service
For efficiency, a single Redis key with NX and an expiry. For correctness, ZooKeeper or etcd, which agree on changes by majority.
Add a lease and renewal
Explain the trade-off: long leases slow recovery, short ones cause false takeovers. Long jobs renew a short lease in the background.
Show the pause problem
Draw a holder that freezes past its lease while a second holder starts. Say why checking the lease before each write cannot fix it.
Add fencing tokens
The lock service hands out a rising number. The database rejects any write with a smaller number than it has already seen.
Make the work idempotent
Give each unit of work a unique key, such as client and month, so a redo by the new holder cannot create a duplicate.
Questions people ask
- Is a Redis lock safe enough for a distributed lock?
- It depends on what a double run costs. A single Redis lock is fine for efficiency, where an overlap only wastes work. For correctness, add fencing tokens that the storage checks. Take them from a consensus-based service such as ZooKeeper or etcd, because a Redis counter can lose its newest value in a failover.
- What is the difference between a lock and a lease?
- A lock gives one holder exclusive access, and a lease is a lock that ends on its own after a set time. Almost every practical distributed lock is a lease, because the service must free it if the holder dies. The catch is that a lease can end while the holder is only frozen and still working.
- What is Redlock?
- Redlock is an algorithm described in the Redis documentation that takes the same lock on several independent Redis nodes, five in its example. The lock counts only if a majority agreed before the lease ran out. Martin Kleppmann argued its safety rests on timing assumptions and that it gives no fencing token. Salvatore Sanfilippo, who designed Redlock, disputed this.
- Why do ZooKeeper locks use sequential nodes?
- Sequential nodes form a fair queue. Each waiting client watches only the node just before its own, so a release wakes exactly one waiter. Without this, every waiter would rush at once when the lock is freed, which the ZooKeeper recipes page calls the herd effect.
- What is the difference between leader election and a distributed lock?
- They use the same machinery but last for different times. A lock usually covers one task and is released when the task ends. Leader election keeps one server in charge for hours or days, renewing its session or lease, until it fails or steps down. Both need fencing for writes.
- How long should a lock lease be?
- Longer than the gap between renewals on your slowest day, and short enough that waiting it out after a crash is acceptable. Measure real renewal times and pauses, then set the lease a few times longer than the renewal gap. No lease length removes the need for fencing.
- Does checking the lease before every write make a lock safe?
- No. The check and the write are two separate steps, and a pause can strike between them. In our test, the frozen server's renewal failed within the same hundredth of a second as its next write, and the write still went through. Only a check inside the storage, such as a fencing token, closes that gap.
Sources and further reading
- How to do distributed locking (Martin Kleppmann, 8 February 2016)
- Distributed Locks with Redis (Redis documentation)
- DELEX command reference (Redis documentation)
- Redis replication (Redis documentation)
- Scripting with Lua: atomic execution (Redis documentation)
- Is Redlock safe? (Salvatore Sanfilippo, antirez.com, 2016)
- ZooKeeper Overview: ensemble and majority (Apache ZooKeeper 3.9)
- ZooKeeper Recipes and Solutions: leader election and locks (Apache ZooKeeper 3.9)
- ZooKeeper Programmer's Guide: sequential nodes, sessions and zxid (Apache ZooKeeper 3.9)
- etcd versus other key-value stores: notes on the usage of lock and lease (etcd v3.7 docs)
- etcd FAQ: Raft, quorum and majority (etcd v3.7 docs)
- etcdctl lock command source: ETCD_LOCK_REV (etcd v3.7.2)