System Design (High-Level Design) Module 20 – Building blocks: control, safety and platform services
Design a lock and coordination service
Design a lock and coordination service on a consensus log, with sessions, keepalives, watches, fencing tokens and clients that stop when sessions expire.
What you will learn
- Design a consensus-backed lock and lease service from requirements and a load estimate
- Specify sessions, keepalives, waiter queues, watches and fencing tokens, with their error cases
- Explain what a client library must do when its session is in doubt and when it has expired
- Size one coordination cell for 120,000 client processes and decide when proxies and more cells are needed
Before you start
What you’ll learn · Before you start
What you will learn
- Design a consensus-backed lock and lease service from requirements and a load estimate
- Specify sessions, keepalives, waiter queues, watches and fencing tokens, with their error cases
- Explain what a client library must do when its session is in doubt and when it has expired
- Size one coordination cell for 120,000 client processes and decide when proxies and more cells are needed
Before you start
On this page
A lock and coordination service is a small, strongly consistent store that many processes use to agree on who holds a lock, who leads a group and what the current configuration is. It replicates every change through a consensus: Agreement among a group of servers on one ordered log of values, reached by majority vote, so that every committed value survives the failure of any minority of the servers. More in “Consensus in practice: what Raft gives you” group of about five servers, so it keeps working while a minority of them fail. Clients hold sessions that they keep alive with heartbeats; locks and membership keys belong to a session and vanish when it expires; watches tell clients when something changes; and every grant carries a 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” that the protected resource checks. Interviewers ask you to design a lock service to hear what pauses, partitions and failovers do to it. Chubby, ZooKeeper and etcd are the best-known examples.
Requirements and estimate: keepalives, not locks, load the cell
A company runs 2,000 services, each with about 60 processes spread over three zones of one region. Schedulers need one active leader per job, deploy tools take maintenance locks, every process announces that it is alive, and routing tables live in small configuration keys. Before you design a lock and coordination service for them, ask:
- What will clients use it for: locks, 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. More in “Leader election, leases and fencing tokens”, membership or configuration?
- How many client processes are there, and how often does a lock change hands?
- How quickly must a dead holder’s lock reach the next process?
- Must every read see the latest write?
- One region or several, and what should a client do when it cannot reach the service?
The answers here: 120,000 client processes, 5,000 locks that change hands about ten times a day each, a dead holder replaced within about 15 seconds, reads of lock state that are never stale, and one region with three zones.
Functional requirements: open and keep alive a session; acquire and release a lock, waiting in a queue; ephemeral keys that vanish with their session; watches on keys and prefixes; small keys with conditional writes; a fencing token with every grant. Non-functional requirements: never two holders as far as a resource that checks tokens can tell; survive the loss of any one zone; acquire within about 20 ms inside the region; and clients that stop acting as holders before the service can give their lock away.
The first estimate:
# A first load estimate for the lock and coordination service of this lesson. Every input is an assumption to
# replace with numbers from your own fleet: the process count from your deploy system, handovers from the service's
# own logs, and watchers from how your client libraries use watches.
SERVICES = 2_000
PROCESSES_PER_SERVICE = 60 # one session each, 20 in each of 3 zones
ZONES = 3
TTL_S = 12 # session lease
RENEWALS_PER_TTL = 3 # renew three times per lease, so one lost reply is not fatal
LOCKS = 5_000 # locks and leader elections in use
HANDOVERS_PER_DAY = 10 # times a lock changes holder on a normal day
ENTRY_BYTES = 200 # a session, lock or membership key with its metadata
CONFIG_KEYS, CONFIG_BYTES = 10_000, 4_000
clients = SERVICES * PROCESSES_PER_SERVICE
keepalives = clients * RENEWALS_PER_TTL / TTL_S
writes = LOCKS * HANDOVERS_PER_DAY / 86_400
entries = clients + LOCKS + clients # sessions, locks, one membership key per process
state_mb = (entries * ENTRY_BYTES + CONFIG_KEYS * CONFIG_BYTES) / 1e6
per_zone = PROCESSES_PER_SERVICE // ZONES
lost = SERVICES * per_zone # the sessions of the zone that fails
survivors = PROCESSES_PER_SERVICE - per_zone
events = lost * survivors # each survivor watches its own service's members
print(f"clients: {SERVICES:,} services x {PROCESSES_PER_SERVICE} processes = {clients:,} sessions")
print(f"keepalives: each renewed every {TTL_S // RENEWALS_PER_TTL} s = {keepalives:,.0f} a second, all at the leader")
print(f"normal writes: {LOCKS:,} locks x {HANDOVERS_PER_DAY} handovers a day = {writes:.1f} a second")
print(f"state: {entries:,} entries x {ENTRY_BYTES} B + {CONFIG_KEYS:,} config keys x {CONFIG_BYTES:,} B = {state_mb:.0f} MB")
print(f"one zone fails: {lost:,} sessions expire within {TTL_S} s = {lost / TTL_S:,.0f} expiry writes a second")
print(f" about {round(LOCKS / ZONES):,} locks change holder; {events:,} membership watch events reach the survivors") Output
clients: 2,000 services x 60 processes = 120,000 sessions keepalives: each renewed every 4 s = 30,000 a second, all at the leader normal writes: 5,000 locks x 10 handovers a day = 0.6 a second state: 245,000 entries x 200 B + 10,000 config keys x 4,000 B = 89 MB one zone fails: 40,000 sessions expire within 12 s = 3,333 expiry writes a second about 1,667 locks change holder; 1,600,000 membership watch events reach the survivors
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 lock_estimate.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
The locks are the smallest load, well under one write a second. Keepalives, 30,000 a second, all land on the leader, and the loss of one zone turns into 3,333 expiry writes a second and 1.6 million watch events. The whole state fits in memory on every server.
Checkpoint
Check the estimate
The first design of the lock and coordination service
Text description of the diagram
The diagram shows the first design from top to bottom.
- At the top is the application side. On the left are 120,000 client processes; each keeps a session, sends keepalives and tracks its own lease deadline. On the right is a protected resource. A dashed arrow from the clients to the resource shows that every write carries the holder's fencing token, and the resource refuses tokens older than one it has seen.
- An arrow from the application side down to the cell carries sessions, lock requests and watches.
- In the middle is the coordination cell: five servers in three zones, the leader and a follower in zone A, two followers in zone B and one follower in zone C.
- An arrow from the cell down to the bottom box shows the committed log entries, applied in log order.
- At the bottom is the state machine that every server keeps: sessions, waiter queues, keys with their revisions, and watches.
Follow one lock through the cell:
The drawing shows the whole scene; its 7 steps are listed below.
Every step as text
- A session. Client A opens a session with a lease of 12 seconds and keeps it alive with keepalives. Everything it holds will belong to that session.
- Asking for the lock. A asks for the lock jobs/leader. The leader appends the request to its log, and once a majority of the five servers has stored the entry, every server applies it in the same order.
- A holds it. Nobody was waiting, so A holds the lock, and the reply carries a fencing token, 7. Every write A sends to the protected resource carries that number.
- B waits. Client B asks for the same lock. It joins the queue of waiters, as one entry, and watches only the entry just ahead of it, A's.
- A releases. A finishes and releases the lock. Deleting its entry is a log entry too, so every server sees the release at the same point in the log.
- The watch fires. B's watch fires, and B now holds the lock with token 8, larger than any token handed out before it.
- The resource decides. A resource that has accepted a write with token 8 refuses any later write that still carries 7, so a slow message from A can never overwrite B's work.
The choices still open, each with its plain meaning:
- A replicated log: every change becomes an entry that a majority of servers stores before any server applies it.
- Sessions with keepalives: a client proves that it is alive by renewing 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” on its session.
- Ephemeral keys: keys that the service deletes when the session that created them ends.
- Waiter queues: each waiting client owns one entry and watches the entry just ahead of it.
- Fencing tokens: a number that grows with every grant, which the protected resource checks.
- Watches: notifications of changes to a key or a prefix, resumable from a revision.
- A grace period: time a client waits without acting before it gives up on its session.
- Proxies: servers that carry many clients’ sessions to the cell as a few.
The deep dives that decide whether this design works:
- What does each call write to the log, and where does the fencing token come from?
- How long should a session last, and what must a client do when it stops hearing from the service?
- What happens to sessions and locks when the leader fails?
- How do 200 clients wait for one lock without a stampede, and what if the reply to an acquire is lost?
- How are reads kept up to date without sending every read through the log?
- How does one cell serve 120,000 processes, and where does it stop scaling?
An answer is judged on requirements and estimates, the replicated state machine, sessions and expiry, client behaviour during failures, fencing, and scaling.
The holder’s create revision is its fencing token Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- Every change is a log entry, except keepalives
- A cut-off holder, a herd and a lost reply, simulated
- A client rides out a failover with jeopardy, a grace period and lease extension
- A watch resumes from a revision, or reads the state again
- Reads that are never stale, and reads that may be
- Keepalives size a cell: longer leases, proxies and observers
- Failure drills: a lost leader, a slow disk, a lost zone, a paused client
- The leader crashes at peak
- The leader’s disk becomes slow
- A zone is cut off
- A client pauses or its clock jumps
- Bottlenecks at 10× and 100×
- Alternatives rejected
- What each level is expected to cover
- A sample answer and a 45-minute self-mock
- Practise: a lock table with sessions and tokens
- Keep a lock table with sessions, waiters and fencing tokens
- Interview questions
- Your coordination service runs five servers for 60,000 client processes with 10-second sessions. After a deploy doubled the number of processes, the leader started missing heartbeats and elections now repeat every few minutes, although lock traffic did not change. What is overloading the leader, and how do you fix it without weakening safety?
- Your coordination service's leader failed and a new one was elected after 15 seconds; sessions last 12 seconds. Thousands of services then lost their leaders at once, opened new sessions and queued for their locks again, and the new leader was overloaded for minutes. Explain what happened and how the service and its client library should behave.
- One lock in your coordination service has 3,000 waiting clients. Every release makes the cell's CPU spike and other calls time out, and twice the lock stayed held after its holder had finished. How are the waiters probably implemented, and how would you redesign acquiring and waiting?
- Key takeaways
3 diagrams · 4 runnable examples (JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 61 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).
Read a complete sample first: Rate-limiting algorithms compared
References
- The Chubby lock service for loosely-coupled distributed systems (USENIX OSDI (Burrows, Google; author's copy))
- ZooKeeper Programmer's Guide (Apache Software Foundation)
- ZooKeeper Recipes and Solutions (Apache Software Foundation)
- ZooKeeper Observers: scaling without hurting write performance (Apache Software Foundation)
- etcd API (revisions, watches and leases) (etcd authors (CNCF))
- etcd API guarantees (etcd authors (CNCF))
- etcd versus other key-value stores (notes on the usage of lock and lease) (etcd authors (CNCF))
- etcd FAQ (etcd authors (CNCF))
- etcd tuning (etcd authors (CNCF))
- etcd system limits (etcd authors (CNCF))
- In Search of an Understandable Consensus Algorithm (Raft, section 8, client interaction) (USENIX ATC (Ongaro and Ousterhout; authors' copy))
- How to do distributed locking (Martin Kleppmann)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress