System Design (High-Level Design) Module 10 – Consistency, time and coordination
Leader election, leases and fencing tokens
How to elect one leader with a coordination service, why a paused leader can outlive its lease, and how fencing tokens stop its stale writes.
What you will learn
- Explain how a paused leader can keep acting after its lease has expired
- Trace leader election with ZooKeeper's ephemeral nodes or etcd's leases and revisions
- Protect a shared resource with fencing tokens checked by the resource itself
- Calculate a lease's renewal interval, guard time and failover gap
- Choose failure-detection timeouts that trade detection speed against false suspicion
Before you start
What you’ll learn · Before you start
What you will learn
- Explain how a paused leader can keep acting after its lease has expired
- Trace leader election with ZooKeeper's ephemeral nodes or etcd's leases and revisions
- Protect a shared resource with fencing tokens checked by the resource itself
- Calculate a lease's renewal interval, guard time and failover gap
- Choose failure-detection timeouts that trade detection speed against false suspicion
Before you start
On this page
Many jobs need exactly one process at a time: the scheduler that starts a nightly report, the owner of a partition, the only writer of a file. If that process holds a 10-second lease and stalls for 15 seconds, a second one takes over meanwhile, and both may write. leader election: Choosing one process out of several to do a job alone, usually through a coordination service, with a lease so that a crashed leader is replaced without anyone's help. picks the one process, usually with the help of a coordination service: A small service replicated with consensus, such as ZooKeeper or etcd, that other systems use for leader election, locks, leases and shared configuration. such as ZooKeeper or etcd. Leases make leadership expire when the leader stops renewing it, so a crashed leader is replaced without anyone’s help. Fencing tokens are numbers that grow with every new leader, and the resource refuses any write that carries an older number than one it has seen. A design needs all three, because leases alone fail in a way that is easy to miss: a leader that is only paused looks exactly like a dead one.
A paused leader outlives its lease, so leases alone are not enough
Leader election on a coordination service takes a few lines. In ZooKeeper each candidate creates an ephemeral node: A ZooKeeper node that exists only as long as the session of the client that created it; when the session ends, ZooKeeper deletes it., sequential node, the lowest sequence number leads, and an ephemeral node vanishes when its owner’s session ends (ZooKeeper recipes; ZooKeeper guide). In etcd each candidate attaches its key to a lease: A time-limited right to act, as a leader or as the holder of a lock. The holder may act only while the lease lasts, and no successor starts before it has expired. More in “Failover, split brain and fencing” that it keeps alive, and the key is deleted when the lease runs out (etcd API). Leases are what make both work, and leases depend on time.
A garbage-collection pause, a stalled virtual machine or a slow network can stop a leader for longer than its lease. The service correctly gives the lease to someone else; the old leader resumes, unaware that any time has passed, and finishes what it was doing. Kleppmann notes that HBase once had exactly this bug (Kleppmann), and Amazon’s Builders’ Library warns that during failures a system may have zero leaders or two (Builders’ Library). Here is the case of the opening, top to bottom in the order things happen:
A paused leader outlives its lease
Text description of the diagram
The diagram shows four participants from left to right: worker A, the lease service, worker B and the storage. Messages are drawn from top to bottom in the order they happen.
- At t=0, worker A asks the lease service for the lease and gets it until t=10.
- At t=4, worker A pauses for 15 seconds, for example in a long garbage-collection pause.
- At t=10, worker B asks for the lease and, since A's has expired, gets it until t=20.
- At t=11, worker B writes version 2 to the storage.
- At t=19, worker A wakes up and writes to the storage too, with an update built on data it read at t=4. Nothing stops this write, so it overwrites B's.
The program replays the diagram with its times:
# Worker A holds a 10-second lease on "report.csv" and pauses (a long garbage-collection pause, say) halfway through
# its update. The lease expires, worker B takes over and writes, then A wakes up and finishes the write it had
# started, believing it still leads. Times are seconds; the storage accepts every write it receives.
LEASE_S = 10
lease = {"holder": None, "expires": 0}
storage = {"report.csv": "v1 (before)"}
log = []
def acquire(worker, t):
if lease["holder"] is None or t >= lease["expires"]:
lease.update(holder=worker, expires=t + LEASE_S)
log.append(f"t={t:>2} {worker} gets the lease until t={t + LEASE_S}")
return True
return False
def write(worker, t, value):
storage["report.csv"] = value
log.append(f"t={t:>2} {worker} writes {value!r}")
acquire("A", 0)
log.append("t= 4 A reads the file and starts its update; then it pauses for 15 s")
for t in range(1, 20):
if acquire("B", t): # B keeps trying; it wins as soon as A's lease has expired
write("B", t + 1, "v2 (from B)")
break
log.append("t=19 A resumes; as far as it knows, no time has passed")
write("A", 19, "v2 (from A, built from the v1 it read at t=4)")
for line in log:
print(line)
print(f"\nThe file now holds {storage['report.csv']!r}: B's update is lost, and two workers acted as leader.") Output
t= 0 A gets the lease until t=10 t= 4 A reads the file and starts its update; then it pauses for 15 s t=10 B gets the lease until t=20 t=11 B writes 'v2 (from B)' t=19 A resumes; as far as it knows, no time has passed t=19 A writes 'v2 (from A, built from the v1 it read at t=4)' The file now holds 'v2 (from A, built from the v1 it read at t=4)': B's update is lost, and two workers acted as leader.
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 two_leaders.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
B’s update is lost, and nothing failed loudly. Checking the lease again just before writing does not help, because the pause can fall between the check and the write. The fixes are structural. fencing token: A number that rises every time a lease or leadership changes hands. The storage refuses a write that carries a lower token than one it has already accepted. More in “Failover, split brain and fencing” let the storage refuse the stale write itself. Lease timing with a monotonic clock, counted from when each renewal was sent, keeps the leader’s own estimate safe. Idempotent work lets a second leader repeat a step without harm. A failure detector, the timeout that declares a leader gone, decides how fast all of this starts. Leader election itself stays simple; the care goes into listing every resource a stale leader could still reach and making each of them refuse it. Fencing tokens do that for anything you can change, which makes them the default for every resource whose corruption you cannot afford.
Key idea
A paused leader cannot tell that its lease ran out, so the resource it writes to has to be the one that refuses it.
Checkpoint
Check: Why leases alone are not enough
ZooKeeper and etcd elect the oldest candidate and drop a leader whose session ends Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- Fencing tokens let the resource refuse an older leader
- Count a lease from the request, on a monotonic clock, and keep a guard
- A failure detector trades detection speed for false alarms
- Practise: a storage that refuses stale leaders
- A storage stub that refuses stale leaders
- Interview questions
- What is a fencing token, and who checks it?
- A billing job must run exactly once a minute across a fleet of 20 servers. How do you choose the server that runs it, and what happens when that server stalls for 30 seconds in the middle of a run?
- How do you choose a lease's duration and its renewal interval? Work through the numbers for a 10-second lease, including clock drift, slow renewals and pauses.
- Your leader sends updates to a third-party API that cannot check fencing tokens. How do you still stop a stale leader from doing damage?
- Key takeaways
4 diagrams · 3 runnable examples (Python) · 1 exercise with sample tests · 3 interview answers · about 39 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Terms in this lesson
- coordination service
- A small service replicated with consensus, such as ZooKeeper or etcd, that other systems use for leader election, locks, leases and shared configuration.
- ephemeral node
- A ZooKeeper node that exists only as long as the session of the client that created it; when the session ends, ZooKeeper deletes it.
- leader election
- Choosing one process out of several to do a job alone, usually through a coordination service, with a lease so that a crashed leader is replaced without anyone's help.
References
- ZooKeeper recipes and solutions (leader election, locks) (The Apache Software Foundation)
- ZooKeeper Programmer's Guide (ephemeral and sequence nodes) (The Apache Software Foundation)
- etcd API reference: key-value, lease and transaction API (etcd (Cloud Native Computing Foundation))
- etcd versus other key-value stores (leases, elections and locks) (etcd (Cloud Native Computing Foundation))
- The Chubby lock service for loosely-coupled distributed systems (USENIX OSDI (Mike Burrows))
- How to do distributed locking (Martin Kleppmann)
- Leader election in distributed systems (Amazon Builders' Library) (Amazon Web Services)
- Spanner: Google's Globally-Distributed Database (USENIX OSDI (Corbett and others))
- SWIM: Scalable Weakly-consistent Infection-style Process Group Membership Protocol (IEEE DSN (Das, Gupta and Motivala))
- SWIM: Scalable Weakly-consistent Infection-style Process Group Membership Protocol (authors' copy) (Cornell University (Das, Gupta and Motivala))
- etcd tuning (heartbeat interval and election timeout) (etcd (Cloud Native Computing Foundation))
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress