Distributed Lock and Coordination System Design Template
Lock and coordination service: Raft consensus, read index, sessions and keep-alives, leader election, fencing tokens, watches, read scaling, misuse policy
사용법: Use it when building or adopting a coordination service for leader election, locks, configuration and discovery. Fill the session, keep-alive and watch counts first, because keep-alives and notification fan-out overload these services before bytes do. The main pitfall is trusting a lock without a fencing token checked by the protected resource.
미리보기
System Design: [Coordination service name]
Reference design for a distributed lock and coordination service: a small, strongly consistent, replicated store that other systems use for leader election, locks with leases, fencing tokens, configuration, membership and change notification. It covers consensus with Raft (or Multi-Paxos), sessions and keep-alives, failover grace periods, watches, read scalability and the ways teams misuse such a service. Concepts draw on the Chubby lock service (Burrows, OSDI 2006), the Raft paper (Ongaro and Ousterhout, USENIX ATC 2014), ZooKeeper (Hunt et al., USENIX ATC 2010) and the etcd v3.6 API documentation; the text and the worked design are original. It follows the structure of the System Design Document template. Replace every
[bracketed]value and every row marked Example.
Document control
| Field | Value |
|---|---|
| Document ID | [SDD-COORD-001] |
| System / feature | [Coordination service for leader election, locks and configuration] |
| Version | [0.1] |
| Status | [Draft / In review / Approved / Implemented / Superseded] |
| Owner | [@owner] |
| Author(s) | [@author] |
| Reviewers | [@reviewer-1], [@reviewer-2] |
| Last updated | YYYY-MM-DD |
| Classification | [Internal; tier-0 dependency] |
| Consistency model | [Example: Linearizable writes and reads by default; serializable reads opt-in] |
| Related | Distributed Key-Value Store System Design (system-design/design-distributed-kv-store), Job Scheduler System Design (system-design/design-job-scheduler), Message Queue System Design (system-design/design-message-queue) |
Approvals
| Role | Name | Decision | Date |
|---|---|---|---|
| Tech lead | [@name] | [Approved / Changes requested] | YYYY-MM-DD |
| Staff / principal engineer | [@name] | [Decision] | YYYY-MM-DD |
| SRE / operations | [@name] | [Decision] | YYYY-MM-DD |
| Security | [@name] | [Decision] | YYYY-MM-DD |
| Platform consumers' representative | [@name] | [Decision] | YYYY-MM-DD |
Revision history
| Version | Date | Author | Change summary |
|---|---|---|---|
| 0.1 | YYYY-MM-DD | [@name] | First draft for design review |
| 0.2 | YYYY-MM-DD | [@name] | Example: Added fencing tokens after a split-brain write in the scheduler |
| 0.3 | YYYY-MM-DD | [@name] | Example: Added proxy tier to absorb keep-alives and watch fan-out |
| 1.0 | YYYY-MM-DD | [@name] | Example: Approved |
Contents
1. Overview
1.1 Problem statement
[Example: Teams implement leader election with database row locks, cache-based locks with TTLs, and hand-written heartbeats. Last quarter two scheduler instances both believed they were leader after a long garbage collection pause and ran the same billing job twice. Configuration is pushed by redeploying, and service discovery lags by minutes.]
1.2 Proposed solution
[Example: A coordination service runs as cells of five replicas across three zones, replicated with Raft. It exposes a small hierarchical key-value store with revisions, sessions held alive by keep-alives, locks and elections built on sessions, and watches that stream changes from a given revision. Every lock acquisition returns a fencing token (the lock key's revision) that protected resources must check. A stateless proxy tier absorbs keep-alives and fans out watch notifications. Values are capped at Example 64 KB and the service is explicitly not a general data store.]
1.3 Decisions requested from reviewers
| ID | Question | Recommended option | Section |
|---|---|---|---|
| Q-01 | Example: Which consensus protocol? | Raft, using a mature open-source implementation | 7.1 |
| Q-02 | Example: How are reads made linearizable without a log write? | Leader confirms leadership with a quorum before serving (read index) | 7.1 |
| Q-03 | Example: What protects resources from a stale lock holder? | Fencing tokens checked by the resource | 7.3 |
| Q-04 | Example: Session TTL and keep-alive interval? | 30 s TTL, keep-alive every 10 s, grace period across leader failover | 7.2 |
| Q-05 | Example: Cell size? | 5 voting replicas in 3 zones | 3.6, 9.1 |
| [Q-nn] | [Question] | [Option] | [Section] |
1.4 45-minute interview path
| Minutes | Cover | Output on the whiteboard |
|---|---|---|
| 0 to 5 | What clients need: leader election, locks, config, discovery; consistency expectation | Core requirements FR-01 to FR-06 |
| 5 to 10 | Sessions, keep-alives, writes, reads, watches | Keep-alives dominate; one leader takes all writes |
| 10 to 18 | Cell, proxies, clients diagram | Section 6 diagram |
| 18 to 33 | Deep dive: consensus and linearizable reads (7.1) and locks with fencing tokens (7.3); if the interviewer asks about scale, use watches and read scaling (7.4) | Raft roles, fencing sequence |
| 33 to 40 | Session failover and misuse pitfalls | Grace period, usage policy |
| 40 to 45 | Trade-offs and 10x | Table in section 10 |
2. Requirements
2.1 Core functional requirements
| ID | Requirement | Priority |
|---|---|---|
| FR-01 | Example: Store small keys and values with a global revision on every change | Must |
| FR-02 | Example: Atomic compare-and-set transactions on one or more keys | Must |
| FR-03 | Example: Sessions with TTL kept alive by clients; ephemeral keys deleted when the session ends | Must |
| FR-04 | Example: Exclusive locks and leader election returning a fencing token | Must |
| FR-05 | Example: Watches on a key or prefix from a given revision, with no missed events | Must |
| FR-06 | Example: Linearizable reads by default | Must |
| FR-07 | Example: Shared (reader) locks | Should |
| FR-08 | Example: Serializable (possibly stale) reads for discovery and config | Should |
| [FR-nn] | [Requirement] | [Must / Should / Could] |
2.2 Non-functional requirements
| ID | Quality | Target | Measured as |
|---|---|---|---|
| NFR-01 | Availability | Example: 99.99% per cell; tolerate loss of one zone | Synthetic lock acquire every 10 s |
| NFR-02 | Write latency | Example: p99 below 25 ms within a region | Server and client histograms |
| NFR-03 | Failover | Example: New leader within 5 s; no healthy client session expires during failover | Failover drill |
| NFR-04 | Safety | Example: Never two holders of an exclusive lock whose fencing tokens are both accepted by the resource | Fault-injection tests with partitions and process pauses |
| NFR-05 | Watch delivery | Example: p99 below 1 s from commit to notification for 100 000 watchers | Probe keys |
| [NFR-nn] | [Quality] | [Target] | [Measurement] |
2.3 Below the line (out of scope)
- [Example: Storing application data, queues or large blobs (see
system-design/design-distributed-kv-storeandsystem-design/design-message-queue)] - [Example: Fine-grained locks held for milliseconds per request]
- [Example: Cross-region linearizability within one cell; each region runs its own cell, with one global cell for rare global decisions]
- [Example: Rate limiting and counters at request rates]
- [Item deferred, with the release that will take it]
3. Capacity estimation
3.1 Assumptions
| Input | Symbol | Value | Source |
|---|---|---|---|
| Client sessions per cell | Example: 100 000 | Service instances x processes | |
| Keep-alive interval | Example: 10 s (TTL 30 s) | 7.2 | |
| Writes per second (locks, elections, config, registrations) | Example: 2 000 | Onboarding survey | |
| Reads per second | Example: 50 000 | Onboarding survey | |
| Watches per session | Example: 5 | Client library defaults | |
| Keys and average value size | , | Example: 1 million, 1 KB | Onboarding survey |
| Log fsync latency on target disks | Example: 2 ms | Benchmark | |
| Notifications sent per second per server | Example: 20 000 | Benchmark |
3.2 Keep-alives
Keep-alives refresh leases, and lease refreshes are handled by the leader. Consequence: keep-alives alone are five times the write rate, so a proxy tier terminates client sessions and refreshes them upstream in batches; the cell sees one stream per proxy instead of one per client.
3.3 Writes
Every write is appended to the replicated log and fsynced by a quorum. One fsync per write would cap throughput near writes/s per replica; group commit batches entries:
Consequence: batching makes 2 000 writes/s easy, but all writes go through one leader, so adding replicas does not add write capacity (it adds latency). Write growth is handled by more cells partitioned by tenant, not bigger cells.
3.4 Reads and watches
Reads at 50 000/s are 25 times the write rate. Consequence: reads must be served by followers and proxies, using read-index confirmation for linearizable reads and local state for serializable ones (7.1, 7.4).
Watches: watches; at about 200 B each that is about 100 MB of memory, which is small. Fan-out is the risk: a change to a key watched by all 100 000 sessions creates 100 000 notifications, which take one server s to send. Consequence: watches register at proxies, so the cell sends one notification per proxy and proxies fan out in parallel; clients add jitter before re-reading so the change does not create a read herd.
3.5 Data size and snapshots
KB GB, kept in memory with the log on SSD. Consequence: cap the keyspace at Example 2 GB; a new or lagging replica installs a snapshot of that size in about 20 s at 100 MB/s, which keeps replacement of a failed replica quick. Enforce value size limits and per-tenant quotas to hold the cap.
3.6 Cell size
A cell of voting replicas tolerates failures. Five replicas across three zones (2 + 2 + 1) survive the loss of any one zone or of any two replicas. Three voters in three zones also survive a zone loss, but only one replica failure. Consequence: five voters, not three, so that one replica can be down for a planned upgrade while another fails unexpectedly.
3.7 Summary
| Quantity | Estimate | Design consequence |
|---|---|---|
| Keep-alives | 10 000/s | Proxy tier aggregates sessions |
| Writes | 2 000/s | Group commit; scale by more cells, not bigger cells |
| Reads | 50 000/s | Follower reads with read index; proxy caches |
| Watch fan-out | 100 000 per hot key change, 5 s from one server | Proxies fan out; client jitter |
| Data | ~1 GB in memory | Keyspace cap 2 GB; value size limit |
| Cell | 5 voters in 3 zones | Survives a zone loss |
4. API design
4.1 Operations
| Operation | Consistency | Notes |
|---|---|---|
Session.Create(ttl) | Linearizable | Returns session ID; server may adjust TTL |
Session.KeepAlive(id) (stream) | Leader | Returns remaining TTL |
KV.Get(key, serializable = false) | Linearizable by default | Returns value, create_revision, mod_revision |
KV.Range(prefix, at_revision?) | Linearizable or at a past revision | Bounded page size |
KV.Txn(compares, then, else) | Linearizable | Compares on value, version or revisions |
Lock.Acquire(name, session, timeout) | Linearizable | Returns fencing token |
Lock.Release(name, token) | Linearizable | Idempotent |
Election.Campaign(name, session, value) | Linearizable | Blocks until leader; returns token |
Election.Observe(name) (stream) | Watch semantics | Current leader and changes |
Watch(key or prefix, from_revision) (stream) | Ordered, resumable | Fails with compacted if the revision is too old |
4.2 Lock acquisition as a transaction
A lock is a key created only if it does not exist, attached to the caller's session so it disappears if the session ends:
Txn(
compare: create_revision("locks/billing-run") == 0
then: put("locks/billing-run", owner="sched-7", session=S123)
else: get("locks/billing-run")
)
-> succeeded: token = header.revision (for example 482113)
-> failed: watch "locks/billing-run" for delete, then retry
For fair queuing, each waiter instead creates locks/billing-run/{session}
and the holder is the key with the lowest create_revision; each waiter
watches only the key just before it, so a release wakes one waiter, not all.
4.3 Protected resource check
The resource, not the lock service, enforces exclusion. It stores the highest token it has accepted per protected object and rejects anything lower:
WriteJobResult(job_id, result, fencing_token = 482113)
if fencing_token < last_seen_token[job_id]: reject (stale holder)
else: last_seen_token[job_id] = fencing_token; apply
4.4 Errors
| Error | Meaning | Client action |
|---|---|---|
session_expired | Session TTL passed without keep-alive | Stop work guarded by its locks; create a new session |
not_leader | Request reached a follower that cannot serve it | Retry via proxy |
compacted | Watch revision older than retained history | Re-read current state, then watch from its revision |
quota_exceeded | Tenant key count or size limit | Reduce usage; request quota |
value_too_large | Above size limit | Store data elsewhere, keep a pointer |
5. Data model
5.1 Replicated state
All state is one replicated state machine per cell, built from the Raft log:
The cell keeps a single 64-bit revision counter that increases with every committed change; it orders all changes and serves as the fencing token source. Watches live in proxy and server memory, not in the replicated state.
5.2 Storage choices
| Data | Store | Why |
|---|---|---|
| Raft log | Append-only files on local SSD, fsynced | Durability of every committed entry |
| State machine | In-memory index plus an embedded B-tree or LSM file with multi-version keys | Fast reads; reads at past revisions until compaction |
| Snapshots | Local files, copied to object storage for disaster recovery | Replica catch-up and restore |
| Session and lease expiry | Leader memory, rebuilt from state on election | Leader alone expires sessions; followers do not |
5.3 Access patterns
| Access pattern | Served by | Key layout and rationale |
|---|---|---|
Who is leader of scheduler? | Lowest create_revision under elections/scheduler/ | Per-candidate keys avoid herds; order by revision is total |
Acquire lock billing-run | Txn on locks/billing-run | One key per lock; attached to session |
| Read all config for a service | Range config/{service}/ | Prefix per service keeps reads to one range |
| List live instances | Range members/{service}/ of ephemeral keys | Session expiry removes dead instances |
| Follow config changes | Watch prefix config/{service}/ from the read revision | Read then watch from that revision: nothing missed |
| Tenant usage | Counters per top-level prefix {tenant}/ | Quotas enforced at write time |
6. High-level design
| Component | Responsibility | Scales by |
|---|---|---|
| Client library | Sessions, keep-alive, lock and election recipes, fencing tokens, watch resume | With clients |
| Proxy tier | Terminate sessions, batch keep-alives, cache reads, share watches | Proxy instances |
| Leader | Order all writes, expire sessions, confirm leadership for reads | One per cell |
| Followers | Replicate log, serve reads with read index, serve watches | Up to 5 voters; non-voting replicas for reads |
| Snapshot backup | Disaster recovery | Object storage |
7. Detailed design
7.1 Deep dive: consensus and linearizable reads
| Option | How it works | Strength | Weakness |
|---|---|---|---|
| A. Primary-backup with external failure detector | One primary, backups copy; an external system picks a new primary | Simple data path | Needs another consensus system to decide failover; split-brain risk |
| B. Multi-Paxos | Replicas agree on each log slot; a stable leader optimises the common case | Proven; flexible | Many variants; hard to implement and reason about |
| C. Raft | Strong leader, randomized election timeouts, log matching, joint consensus for membership | Designed for understandability; many mature implementations | Leader is a throughput bottleneck |
| D. Atomic broadcast as in ZooKeeper (ZAB) | Leader broadcasts ordered updates; recovery protocol on leader change | Proven at scale | Tied to one implementation family |
Choice: C, using a mature implementation rather than writing one. Reason: the team must operate and debug the protocol during incidents; Raft's structure (leader election, log replication, safety) is easier to reason about, and its membership-change procedure supports replacing replicas without downtime.
Linearizable reads have three options:
| Read option | How | Cost | Risk |
|---|---|---|---|
| Through the log | Append a read entry and wait for commit | One quorum write per read | Slow, expensive |
| Read index | Leader records commit index, confirms it is still leader with a quorum heartbeat, then the replica serves once it has applied up to that index | One round of heartbeats, batched across reads | None beyond normal Raft assumptions |
| Leader lease | Leader serves reads alone while its lease (based on election timeout) has not expired | No network round | Depends on bounded clock drift; unsafe if clocks jump |
Choice: read index by default, leader lease off. Reason: the extended Raft paper describes confirming leadership with a majority before answering a read and notes that relying on a lease makes safety depend on timing; for a lock service, safety wins over a fraction of a millisecond. A new leader commits a no-op entry at the start of its term before serving reads, so it knows which entries are committed.
7.2 Deep dive: sessions, keep-alives and failover
Sessions tie ephemeral keys and locks to a live client. A session ends when no keep-alive arrives before its TTL; then its ephemeral keys and locks are deleted in one committed transaction, so watchers see a consistent change.
| Parameter | Example value | Reasoning |
|---|---|---|
| Session TTL | 30 s | Longer than leader failover plus network blips |
| Keep-alive interval | 10 s | Three chances before expiry |
| Election timeout | 1 to 2 s randomized | Cross-zone round trips; Raft paper suggests 150 to 300 ms for LAN-scale clusters |
| Client grace period | 45 s of retrying after losing contact | Survive failover; resume only if the cell confirms the session still exists |
During leader failover no keep-alive is acknowledged. Two measures keep healthy sessions alive:
- A new leader does not expire any session until it has given every session at least one full TTL from its election (session expiry is extended on leadership change).
- The client library, on losing contact, enters a "jeopardy" state: it tells the application that its locks may be at risk, pauses work that needs them, and keeps trying other replicas for the grace period. If contact returns and the cell confirms the session is still alive, work resumes; if the session expired or the grace period ends, the application treats its locks as lost.
The application must also stop acting on a lock when its own process paused (garbage collection, VM migration). Timeouts on the client cannot detect that reliably, which is why fencing tokens (7.3) exist.
7.3 Deep dive: locks, leader election and fencing tokens
A lock gives mutual exclusion in the coordination service, not at the resource. A holder can be paused after it last checked its session, wake up after the session expired and another client took the lock, and still send a write.
| Option | How it works | Protects against paused holder? |
|---|---|---|
| A. TTL lock in a cache | Set-if-absent with expiry | No; also unsafe on cache failover |
| B. Coordination-service lock only | Session-bound key | No; only the service knows who holds it |
| C. Lock-delay | After a holder's session expires, the lock cannot be taken for a short period | Reduces risk; does not remove it |
| D. Fencing token checked by the resource | Token = lock key's revision; resource rejects lower tokens | Yes, if every write path checks it |
Choice: D for every lock that guards a side effect, with C as an extra margin for resources that cannot check tokens yet. Reason: only the resource can reject a stale write after the fact. Teams that cannot add a token check must design the work to be idempotent instead.
Leader election uses the same recipe: each candidate creates an ephemeral key
under elections/{name}/; the lowest create_revision leads and its
revision is the leader epoch, used as the fencing token for everything the
leader writes. Followers watch only their predecessor.
7.4 Deep dive: watches, caching and read scalability
| Option | How it works | Strength | Weakness |
|---|---|---|---|
| A. Polling | Clients re-read every few seconds | Simple | Load grows with clients; staleness equals interval |
| B. One-shot watches | Notification once; client re-reads and re-registers | Simple server | Changes between notification and re-register must be detected by version check |
| C. Revision-based streaming watches | Stream every change after a given revision; resumable after disconnect | Nothing missed; ordered | History must be retained until compaction |
| D. Client cache with server invalidation | Server invalidates cached entries before a write completes | Very low read load | Write waits for invalidations; complex |
Choice: C, served by followers and proxies, plus small proxy caches for
serializable reads. Reason: read-then-watch-from-revision gives clients a
gap-free view; proxies merge identical watches so the cell sees one per proxy.
Keep history for Example 1 hour; a watcher that falls further behind gets
compacted and must re-read.
Read scaling ladder, in order:
- Serializable reads from any follower for discovery and config that tolerate staleness.
- Read-index linearizable reads on followers.
- Proxy caches kept current by watches.
- Non-voting replicas (learners) that replicate the log and serve reads without slowing commits.
- More cells, partitioned by tenant.
7.5 Misuse pitfalls and usage policy
| Pitfall | Why it hurts | Policy |
|---|---|---|
| Using the service as a database | Large values and high write rates slow every tenant's elections | Value cap 64 KB; per-tenant quotas; reviews for any write rate above 50/s |
| Fine-grained locks per request | Write rate explodes; latency on every request | Coarse locks held for minutes or hours; per-request coordination elsewhere |
| Lock without fencing | Paused holder corrupts the resource | Fencing token or idempotent work required |
| Everyone watches one key and re-reads at once | Notification and read herd | Proxies; jitter; watch predecessor only in queues |
| Short session TTLs "for faster failover" | Sessions expire during routine leader changes | TTL at least 3x keep-alive and above failover time |
| Treating a cell as global | Cross-region latency on every write | One cell per region; a global cell only for rare decisions |
| Assuming the service is always up | Hard dependency in every request path | Cache last known config; leaders keep working through short outages only when fenced |
7.6 How real systems do it
How real systems do it (paraphrased, not copied). Burrows describes Google's Chubby in "The Chubby Lock Service for Loosely-Coupled Distributed Systems" (OSDI 2006). Chubby is aimed at coarse-grained locking and small files, with cells of typically five replicas that use Paxos to elect a master holding a master lease. Clients keep sessions alive with keep-alives; when a client loses contact, its library signals jeopardy and waits through a grace period (45 s by default in the paper) so sessions survive master failover. Lock holders can obtain a sequencer that servers check before acting, with a lock-delay as a weaker fallback for servers that do not check. Clients cache data and the master invalidates caches before changes complete; proxies can absorb keep-alives and reads. Separately, Ongaro and Ousterhout's "In Search of an Understandable Consensus Algorithm" (USENIX ATC 2014) defines Raft, decomposing consensus into leader election, log replication and safety; its extended version covers linearizable read handling. This template reuses the ideas, not the text or figures.
8. Scalability and performance
| Concern | Approach | Limit before redesign |
|---|---|---|
| Writes | Group commit; small values | Leader disk and CPU; Example: about 10 000 small writes/s per cell |
| Keep-alives | Proxy aggregation | Proxy count and session churn on proxy restarts |
| Reads | Read index on followers, proxies, learners | Heartbeat batching on the leader |
| Watches | Proxy sharing, jitter | Notification fan-out per server |
| Data size | Quotas, 2 GB cap | Snapshot install time for new replicas |
| Tenants | Separate cells per tenant group | Operational cost per cell |
Write latency budget (NFR-02, Example): client to proxy 0.5 ms, proxy to leader 0.5 ms, leader append and parallel replication to the fastest two followers across zones 2 ms plus fsync 2 ms, apply 0.2 ms, response path 1 ms; about 6 ms typical, with headroom for cross-zone variance up to the 25 ms p99.
9. Reliability, security and operations
9.1 Failure-mode table
| Component | Failure | Detection | Blast radius | Mitigation | RTO |
|---|---|---|---|---|---|
| Leader | Crash | Followers miss heartbeats | Writes and linearizable reads pause | Raft election; sessions extended on new leader | Example: under 5 s |
| One zone | Outage | Replica health | Up to two of five voters | Remaining three keep quorum | No interruption beyond election |
| Two zones | Outage | Quorum lost | Whole cell read-only or down | Clients run on cached config; documented manual recovery from snapshot | Example: 1 h manual |
| Disk | Slow fsync | Commit latency | All writes | Dedicated SSD; alert; replace replica | Example: 30 min |
| Proxy | Crash | Health checks | Its sessions must reconnect | Clients reconnect to another proxy within the grace period | Example: 10 s |
| Client | Long pause | Session expiry | Its locks move to another client | Fencing tokens reject its late writes | Immediate |
| Tenant | Abusive write rate | Per-tenant metrics | Every tenant's latency | Quotas and rate limits per tenant | Example: 1 min |
| [Component] | [Failure] | [Signal] | [Scope] | [Mitigation] | [Time] |
9.2 Security
| Threat | Control |
|---|---|
| Unauthorised lock or config change | mTLS for clients and peers; role-based access per prefix |
| Peer impersonation | Peer certificates pinned to cell membership |
| Secrets stored as config values | Prohibited by policy; secret manager instead; scanner on writes |
| Snapshot theft | Encrypted backups with restricted access |
| Denial of service by one tenant | Quotas, request limits, separate cells for critical tenants |
| [Threat] | [Control] |
9.3 Observability
| Signal | Metric | Alert threshold |
|---|---|---|
| Leadership | Leader changes per hour | Example: more than 3 |
| Commit latency | p99 proposal to commit | Above 25 ms for 5 min |
| Follower lag | Applied index behind leader | Above 1 000 entries |
| Sessions | Expirations per minute | Spike above baseline x 5 |
| Watches | Notification delay | Above 1 s at p99 |
| Size | Keyspace bytes vs cap | Above 70% |
10. Trade-offs and alternatives
| Decision | Options | Chosen | Why | What we give up |
|---|---|---|---|---|
| Consensus | Primary-backup; Multi-Paxos; Raft; ZAB-like | Raft | Understandable, mature implementations | Leader-bound writes |
| Linearizable reads | Via log; read index; leader lease | Read index | Safe without clock assumptions | One heartbeat round per read batch |
| Lock safety | Lock only; lock-delay; fencing | Fencing (+ lock-delay fallback) | Only the resource can reject stale writes | Every resource must check tokens |
| Notifications | Polling; one-shot; streaming from revision | Streaming from revision | Gap-free and resumable | History retention |
| Build or adopt | Write own; adopt etcd, ZooKeeper or Consul; managed | [Choice] | [Reason] | [Cost] |
| At 10x load | 1 million sessions, 20 000 writes/s | Partition by tenant into several cells; more proxies; learners for reads | One leader cannot take 10x writes | More cells to run and to place tenants on |
| At 0.1x load | 10 000 sessions, 200 writes/s | One 3-voter cell may suffice if one zone loss is the only target; no proxy tier | Lower cost | Cannot tolerate a second failure while one replica is down for maintenance |
11. Delivery plan and risks
11.1 Milestones
| Milestone | Scope | Exit criterion | Target date |
|---|---|---|---|
| M1 | Cell, client library with sessions, locks and fencing tokens | Safety test suite passes with partitions and pauses | YYYY-MM-DD |
| M2 | Scheduler and job runner migrate (system-design/design-job-scheduler) | No duplicate runs in 30 days | YYYY-MM-DD |
| M3 | Proxy tier, watches, config and discovery | 100 000 sessions in load test; NFR-05 met | YYYY-MM-DD |
| M4 | Second cell, tenant placement, quotas | Failover drill per cell passes | YYYY-MM-DD |
11.2 Rollout and rollback
[Example: Each consumer migrates behind a flag that selects the old or new election mechanism; both run in observe-only mode for a week and must agree on the leader. Rollback flips the flag; the old mechanism stays deployed until the migration is complete for 30 days.]
11.3 Risks
| ID | Risk | Likelihood | Impact | Mitigation | Owner |
|---|---|---|---|---|---|
| R-01 | Example: Teams skip fencing token checks | High | High | Library makes token mandatory; review checklist | [@name] |
| R-02 | Example: Service becomes a dumping ground for data | Medium | High | Quotas, value cap, usage policy (7.5) | [@name] |
| R-03 | Example: Hot watch key overloads proxies | Medium | Medium | Jitter, watch sharing, load test | [@name] |
| R-04 | Example: Quorum loss in a regional disaster | Low | High | Cached config in clients; restore runbook | [@name] |
| [R-nn] | [Risk] | [L / M / H] | [L / M / H] | [Mitigation] | [@name] |
12. Appendices
12.1 Open questions
| ID | Question | Owner | Due |
|---|---|---|---|
| OQ-01 | Example: Which existing resources can check fencing tokens, and which need idempotency instead? | [@name] | YYYY-MM-DD |
| OQ-02 | Example: Do we need a global cell, or can all decisions be regional? | [@name] | YYYY-MM-DD |
| [OQ-nn] | [Question] | [@name] | YYYY-MM-DD |
12.2 Glossary
| Term | Meaning |
|---|---|
| Cell | One replicated group of servers that forms a quorum |
| Session | Client's liveness lease, renewed by keep-alives |
| Ephemeral key | Key deleted automatically when its session ends |
| Fencing token | Monotonic number from the lock grant that resources compare to reject stale holders |
| Read index | Commit index the leader confirms with a quorum before a linearizable read is served |
| Jeopardy | Client state after losing contact, before the session is declared lost |
| Learner | Non-voting replica that receives the log and serves reads |
| Compaction | Removal of old revisions from history |
| [Term] | [Meaning] |
12.3 Related templates
- Distributed Key-Value Store System Design (
system-design/design-distributed-kv-store): for data that does not belong here. - Job Scheduler System Design (
system-design/design-job-scheduler): main consumer of leader election and fencing. - Message Queue System Design (
system-design/design-message-queue): controller election and partition metadata. - Stream Processing Engine System Design (
system-design/design-stream-processing-engine): coordinator leader and checkpoint pointer. - Distributed Cache System Design (
system-design/design-distributed-cache): slot map storage.
12.4 References
| Reference | Use |
|---|---|
| Burrows M., "The Chubby Lock Service for Loosely-Coupled Distributed Systems", OSDI '06, 2006 | Coarse locks, sessions, grace period, sequencers, lock-delay, proxies (7.2, 7.3, 7.6) |
| Ongaro D., Ousterhout J., "In Search of an Understandable Consensus Algorithm", USENIX ATC '14, 2014, and its extended version | Raft, election timeouts, read-only handling (7.1, 7.6) |
| Hunt P. et al., "ZooKeeper: Wait-free Coordination for Internet-scale Systems", USENIX ATC '10, 2010 | Watches and coordination recipes (7.4) |
| etcd v3.6 documentation, API guarantees and API reference | Revisions, leases, watches from a revision, transactions (4, 5, 7.4) |
| Kleppmann M., "How to do distributed locking", 8 February 2016 | Fencing token argument (7.3) |
Standard and licence note
Based on the publicly described designs and documentation cited in 12.4 (Chubby, Raft, ZooKeeper, etcd v3.6); no standard governs coordination services. This is a reference design to adapt, with illustrative numbers marked Example. Original text written for this collection; sources were used as inspiration only and no text, figures or diagrams were copied. Check the current documentation of any implementation you adopt, because API details change between releases. Template licence: MIT.
관련 템플릿
Distributed Cache System Design Template
Distributed cache: cache-aside, delete on write, leases against herds, commit-log invalidation across regions, slot maps, hot keys, gutter pool, warm-up
시스템 설계Distributed Key-Value Store System Design Template (Dynamo-Style)
Consistent hashing, vnodes, quorums (R+W>N), vector clocks vs LWW, hinted handoff, Merkle anti-entropy, gossip, LSM compaction, cache eviction, hot keys
시스템 설계Zanzibar-Style Authorization Service System Design Template
Relationship-based authorization service: relation tuples, namespace config, Check/Expand/List/Watch APIs, consistency tokens, caching, sharding, failure modes