{"id":"consensus","title":"Consensus, leader election and coordination","sections":[{"id":"overview","title":"Overview","html":"<p>Many designs quietly assume that some decision is made exactly once: which database node is the primary, which worker owns a shard, who holds a lock, whether a config change happened. On one machine that's trivial. Across several machines that can crash and lose messages, getting a group to agree on one answer, and never later disagree about it, is the <strong>consensus</strong> problem.</p>\n<p>You will rarely implement consensus yourself. You will use systems built on it: etcd, ZooKeeper, Consul, and the replication inside databases like CockroachDB and Spanner. Interviewers want to see that you know when you need it, what it costs, and how to use a coordination service safely.</p>","headings":[]},{"id":"why-consensus-is-needed-split-brain","title":"Why consensus is needed: split brain","html":"<h3 id=\"why-consensus-is-needed-split-brain\">Why consensus is needed: split brain</h3>\n<p>The primer's <a href=\"/learn/availability-patterns#fail-over\">fail-over</a> section describes active-passive pairs: the passive node watches the active one's heartbeat and takes over when it stops. Now suppose the network between them fails, but both nodes are fine and clients can still reach both. The passive node sees no heartbeat and promotes itself. The old active node never crashed, so it keeps accepting writes. You now have two primaries. This is <strong>split brain</strong>.</p>\n<p>Both sides now accept conflicting writes: the last concert seat is sold on each side. When the network heals, there is no clean way to merge the two histories, and some writes get thrown away or need manual repair.</p>\n<p>The root problem is that a node can't tell \"the other node is dead\" from \"I can't reach the other node\". Timeouts can't distinguish them. So a node should never decide alone that it is the leader. It needs agreement from a group, arranged so that two competing nodes can't both get it.</p>","headings":[{"id":"why-consensus-is-needed-split-brain","title":"Why consensus is needed: split brain","depth":3}]},{"id":"quorums-and-majorities","title":"Quorums and majorities","html":"<h3 id=\"quorums-and-majorities\">Quorums and majorities</h3>\n<p>The arrangement that makes this work is the <strong>majority quorum</strong>. With N voting nodes, a decision needs floor(N/2) + 1 of them. Any two majorities of the same group share at least one node, so two candidates can't both collect a majority for the same term, and a new leader always finds at least one node that knows what the old majority decided.</p>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<div class=\"table-wrap\"><table><thead><tr><th>Cluster size</th><th>Majority</th><th>Failures tolerated</th></tr></thead><tbody><tr><td>1</td><td>1</td><td>0</td></tr><tr><td>2</td><td>2</td><td>0</td></tr><tr><td>3</td><td>2</td><td>1</td></tr><tr><td>4</td><td>3</td><td>1</td></tr><tr><td>5</td><td>3</td><td>2</td></tr><tr><td>7</td><td>4</td><td>3</td></tr></tbody></table></div>\n<p>Two lessons come from the table. A cluster of 2f + 1 nodes survives f failures. And even sizes don't help: 4 nodes tolerate the same single failure as 3, but need one more vote per write. That's why consensus clusters are almost always 3 or 5 nodes. More nodes add fault tolerance but make every write wait for more acknowledgments.</p>\n<p>In a partition, the side holding a majority keeps working and the minority side stops accepting writes. That is the CP choice from the <a href=\"/learn/availability-vs-consistency#cap-theorem\">CAP theorem</a>: the minority side gives up availability so the system never has two histories.</p>\n<p>Quorums also set latency. A write commits when the leader hears back from enough followers to form a majority, so commit time is roughly the round trip to the follower that completes the majority. Assume a 5-node cluster spread across three regions as 2 + 2 + 1, with the leader in region A. Assume round trips of 1 ms to its peer in region A, 30 ms to both nodes in region B, and 70 ms to region C. The leader needs 2 follower acknowledgments (3 of 5 including itself): the region A peer at 1 ms and a region B node at 30 ms. Commits take about 30 ms plus disk writes, and losing all of region C doesn't change that.</p>","headings":[{"id":"quorums-and-majorities","title":"Quorums and majorities","depth":3}]},{"id":"raft-at-the-intuition-level","title":"Raft, at the intuition level","html":"<h3 id=\"raft-at-the-intuition-level\">Raft, at the intuition level</h3>\n<p><strong>Raft</strong> is the consensus algorithm behind etcd, Consul, CockroachDB and Kafka's KRaft mode, among others. Its authors designed it to be easier to understand than Paxos. It solves consensus by keeping a <strong>replicated log</strong>: every node holds the same sequence of commands, applies them in order to its own copy of a state machine (say, a key-value map), and so ends up in the same state.</p>\n<h4 id=\"terms-and-leader-election\">Terms and leader election</h4>\n<p>Time is divided into numbered <strong>terms</strong>. Each term starts with an election and has at most one leader. Every message carries the sender's term. A node that sees a higher term than its own updates its term and, if it was leader or candidate, steps down to follower.</p>\n<p>Followers expect regular heartbeats from the leader. If a follower's <strong>election timeout</strong> passes without one, it becomes a candidate: it increments the term, votes for itself, and asks the others for votes. Each node votes for at most one candidate per term, and only for a candidate whose log is at least as up to date as its own. A candidate that collects a majority becomes leader. The timeouts are randomized (the Raft paper suggests something like 150 to 300 ms), so usually one node times out first and wins before others start competing.</p>\n<p>The \"up to date\" rule is what keeps committed data safe: a committed entry is on a majority, a winner needs votes from a majority, so some voter has the entry and refuses any candidate missing it.</p>\n<h4 id=\"log-replication-and-the-commit-index\">Log replication and the commit index</h4>\n<p>Clients send writes to the leader. The leader appends the command to its log and sends it to followers. Once a majority of nodes (counting the leader) have stored the entry, it is <strong>committed</strong>: it will never be lost or replaced. The leader tracks the highest committed position as its <strong>commit index</strong>, applies committed entries to its state machine, replies to the client, and includes the commit index in later messages so followers apply the entries too.</p>\n<p>One subtlety, in case an interviewer probes: a new leader only counts replicas this way for entries from its own term. Entries left over from earlier terms become committed indirectly, once an entry from the current term commits after them (section 5.4.2 of the Raft paper explains why).</p>\n<figure class=\"diagram\" data-text=\"Client -> Leader, term 4 log to entry 8 (write); Leader, term 4 log to entry 8 -> Follower A has 8 (append 8); Leader, term 4 log to entry 8 -> Follower B has 8 (append 8); Leader, term 4 log to entry 8 -> Follower C slow, has 6 (append 7, 8); Leader, term 4 log to entry 8 -> Follower D unreachable (retrying); Committed: leader + A + B = 3 of 5\"><div class=\"diagram-scroll\"><svg class=\"dg\" viewBox=\"0 0 720 376\" width=\"720\" role=\"img\" aria-label=\"Entry 8 commits once 3 of 5 nodes have stored it\"><defs><marker id=\"dg-arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path class=\"dg-arrowhead\" d=\"M0,1 L9,5 L0,9 z\"></path></marker></defs><line class=\"dg-edge\" x1=\"151\" y1=\"186\" x2=\"247\" y2=\"186\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"384.4\" y1=\"157\" x2=\"571.1\" y2=\"73\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"391\" y1=\"175.4\" x2=\"567\" y2=\"149\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"391\" y1=\"196.7\" x2=\"567\" y2=\"223.1\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge dg-dashed\" x1=\"384.4\" y1=\"215\" x2=\"571.1\" y2=\"299\" marker-end=\"url(#dg-arrow)\"></line><rect class=\"dg-node dg-client\" x=\"12\" y=\"162\" width=\"136\" height=\"48\" rx=\"24\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"80\" y=\"190.5\">Client</tspan></text><rect class=\"dg-node dg-box\" x=\"252\" y=\"160\" width=\"136\" height=\"52\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"320\" y=\"182.5\">Leader, term 4</tspan><tspan x=\"320\" y=\"198.5\">log to entry 8</tspan></text><rect class=\"dg-node dg-box\" x=\"572\" y=\"16\" width=\"136\" height=\"52\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"640\" y=\"38.5\">Follower A</tspan><tspan x=\"640\" y=\"54.5\">has 8</tspan></text><rect class=\"dg-node dg-box\" x=\"572\" y=\"112\" width=\"136\" height=\"52\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"640\" y=\"134.5\">Follower B</tspan><tspan x=\"640\" y=\"150.5\">has 8</tspan></text><rect class=\"dg-node dg-box\" x=\"572\" y=\"208\" width=\"136\" height=\"52\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"640\" y=\"230.5\">Follower C</tspan><tspan x=\"640\" y=\"246.5\">slow, has 6</tspan></text><rect class=\"dg-node dg-box\" x=\"572\" y=\"304\" width=\"136\" height=\"52\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"640\" y=\"326.5\">Follower D</tspan><tspan x=\"640\" y=\"342.5\">unreachable</tspan></text><rect class=\"dg-node dg-note\" x=\"252\" y=\"296\" width=\"136\" height=\"68\" rx=\"8\"></rect><text class=\"dg-label dg-muted\" text-anchor=\"middle\"><tspan x=\"320\" y=\"318.5\">Committed:</tspan><tspan x=\"320\" y=\"334.5\">leader + A + B</tspan><tspan x=\"320\" y=\"350.5\">= 3 of 5</tspan></text><rect class=\"dg-edge-bg\" x=\"178.5\" y=\"177\" width=\"41\" height=\"18\" rx=\"4\"></rect><text class=\"dg-edge-label\" x=\"199\" y=\"190\" text-anchor=\"middle\">write</text><rect class=\"dg-edge-bg\" x=\"448\" y=\"106\" width=\"59.6\" height=\"18\" rx=\"4\"></rect><text class=\"dg-edge-label\" x=\"477.8\" y=\"119\" text-anchor=\"middle\">append 8</text><rect class=\"dg-edge-bg\" x=\"449.2\" y=\"153.1\" width=\"59.6\" height=\"18\" rx=\"4\"></rect><text class=\"dg-edge-label\" x=\"479\" y=\"166.1\" text-anchor=\"middle\">append 8</text><rect class=\"dg-edge-bg\" x=\"439.9\" y=\"200.9\" width=\"78.2\" height=\"18\" rx=\"4\"></rect><text class=\"dg-edge-label\" x=\"479\" y=\"213.9\" text-anchor=\"middle\">append 7, 8</text><rect class=\"dg-edge-bg\" x=\"448\" y=\"248\" width=\"59.6\" height=\"18\" rx=\"4\"></rect><text class=\"dg-edge-label\" x=\"477.8\" y=\"261\" text-anchor=\"middle\">retrying</text></svg></div><figcaption>Entry 8 commits once 3 of 5 nodes have stored it</figcaption></figure>\n<p>Each append message also names the position and term of the entry just before the new ones. A follower whose log doesn't match there rejects the message, and the leader steps back until they agree, then overwrites the follower's conflicting entries. That is how lagging followers, and leftovers from a failed leader, get repaired.</p>\n<p>An old leader cut off in a minority partition can't commit anything. When it reconnects and sees a higher term, it steps down and its uncommitted entries are overwritten. Reads need care too, since a deposed leader answering from local state could return stale data. etcd's default reads confirm leadership with a majority first, and it offers cheaper \"serializable\" reads that may be stale.</p>\n<h4 id=\"paxos-and-flp\">Paxos and FLP</h4>\n<p><strong>Paxos</strong>, from Leslie Lamport, came first and underlies Google's Chubby and Spanner; Multi-Paxos gives the same guarantees as Raft, and ZooKeeper uses a related protocol called Zab. In an interview, \"a Raft or Paxos group\" is enough detail. The <strong>FLP result</strong> (Fischer, Lynch and Paterson, 1985) proves no deterministic algorithm can guarantee consensus terminates in a fully asynchronous network if even one node may crash, which is why real systems stay safe always but rely on timeouts to make progress.</p>","headings":[{"id":"raft-at-the-intuition-level","title":"Raft, at the intuition level","depth":3},{"id":"terms-and-leader-election","title":"Terms and leader election","depth":4},{"id":"log-replication-and-the-commit-index","title":"Log replication and the commit index","depth":4},{"id":"paxos-and-flp","title":"Paxos and FLP","depth":4}]},{"id":"leases-and-fencing-tokens","title":"Leases and fencing tokens","html":"<h3 id=\"leases-and-fencing-tokens\">Leases and fencing tokens</h3>\n<p>A <strong>lease</strong> is a lock with an expiry. A node that holds a lease may act as leader (or owner of a resource) until the lease runs out, and must renew it to keep going. If the holder crashes, the lease expires and someone else can take over, without anyone needing to know whether the holder is dead or just unreachable.</p>\n<p>Leases rely on time, and the holder can't fully control it. Suppose worker A holds a 30-second lease on shard 12 and sends a write to storage, but the write gets stuck: the host is overloaded, or the packet sits in a retry queue somewhere on the network. Forty seconds pass. A's lease expires, worker B takes over shard 12 and starts writing. Then A's delayed write finally arrives, and storage has no way to tell it's stale. Process pauses (garbage collection, a VM being migrated) cause the same problem from the other direction: the holder wakes up and acts on a lease it no longer has.</p>\n<p>The fix is a <strong>fencing token</strong>: a number that increases every time the lock is granted. The client includes the token with every write, and the storage service remembers the highest token it has seen and rejects anything lower.</p>\n<figure class=\"diagram\" data-text=\"Lock service -> Worker A delayed, token 117 (grant 117); Lock service -> Worker B token 118 (grant 118); Worker B token 118 -> Storage highest seen: 118 (write, 118 OK); Worker A delayed, token 117 -> Storage highest seen: 118 (write, 117 rejected)\"><div class=\"diagram-scroll\"><svg class=\"dg\" viewBox=\"0 0 480 277\" width=\"480\" role=\"img\" aria-label=\"The storage rejects a write carrying an older fencing token\"><defs><marker id=\"dg-arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path class=\"dg-arrowhead\" d=\"M0,1 L9,5 L0,9 z\"></path></marker></defs><line class=\"dg-edge\" x1=\"195\" y1=\"69\" x2=\"131.7\" y2=\"107\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"285\" y1=\"69\" x2=\"348.3\" y2=\"107\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"351.7\" y1=\"167\" x2=\"300\" y2=\"198\" marker-end=\"url(#dg-arrow)\"></line><line class=\"dg-edge\" x1=\"128.3\" y1=\"167\" x2=\"180\" y2=\"198\" marker-end=\"url(#dg-arrow)\"></line><rect class=\"dg-node dg-box\" x=\"172\" y=\"18\" width=\"136\" height=\"48\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"240\" y=\"46.5\">Lock service</tspan></text><rect class=\"dg-node dg-box\" x=\"12\" y=\"112\" width=\"136\" height=\"52\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"80\" y=\"134.5\">Worker A</tspan><tspan x=\"80\" y=\"150.5\">delayed, token 117</tspan></text><rect class=\"dg-node dg-box\" x=\"332\" y=\"112\" width=\"136\" height=\"52\" rx=\"8\"></rect><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"400\" y=\"134.5\">Worker B</tspan><tspan x=\"400\" y=\"150.5\">token 118</tspan></text><path class=\"dg-node dg-store\" d=\"M172,210 a68,7 0 0,1 136,0 v48 a68,7 0 0,1 -136,0 z\"></path><path class=\"dg-line\" d=\"M172,210 a68,7 0 0,0 136,0\"></path><text class=\"dg-label\" text-anchor=\"middle\"><tspan x=\"240\" y=\"233.5\">Storage</tspan><tspan x=\"240\" y=\"249.5\">highest seen: 118</tspan></text><rect class=\"dg-edge-bg\" x=\"130.4\" y=\"79\" width=\"65.8\" height=\"18\" rx=\"4\"></rect><text class=\"dg-edge-label\" x=\"163.3\" y=\"92\" text-anchor=\"middle\">grant 117</text><rect class=\"dg-edge-bg\" x=\"283.8\" y=\"79\" width=\"65.8\" height=\"18\" rx=\"4\"></rect><text class=\"dg-edge-label\" x=\"316.7\" y=\"92\" text-anchor=\"middle\">grant 118</text><rect class=\"dg-edge-bg\" x=\"280.5\" y=\"173.5\" width=\"90.6\" height=\"18\" rx=\"4\"></rect><text class=\"dg-edge-label\" x=\"325.8\" y=\"186.5\" text-anchor=\"middle\">write, 118 OK</text><rect class=\"dg-edge-bg\" x=\"90.3\" y=\"173.5\" width=\"127.8\" height=\"18\" rx=\"4\"></rect><text class=\"dg-edge-label\" x=\"154.2\" y=\"186.5\" text-anchor=\"middle\">write, 117 rejected</text></svg></div><figcaption>The storage rejects a write carrying an older fencing token</figcaption></figure>\n<p>Fencing needs the resource to cooperate. A database can check a token in a conditional write (<code>UPDATE ... SET fence_token = 118, ... WHERE id = ? AND fence_token &#x3C;= 118</code>). An external API that knows nothing about your tokens can't, and then the lock alone can't prevent overlapping writes. Coordination services provide suitable numbers: ZooKeeper's transaction IDs and znode versions, and etcd's revision numbers, both of which only increase.</p>","headings":[{"id":"leases-and-fencing-tokens","title":"Leases and fencing tokens","depth":3}]},{"id":"zookeeper-and-etcd-in-practice","title":"ZooKeeper and etcd in practice","html":"<h3 id=\"zookeeper-and-etcd-in-practice\">ZooKeeper and etcd in practice</h3>\n<p><strong>ZooKeeper</strong> and <strong>etcd</strong> are small, strongly consistent stores replicated with consensus. Their job is to hold small amounts of critical metadata, typically kilobytes per key, not application data. A few features make them useful for coordination:</p>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<div class=\"table-wrap\"><table><thead><tr><th>Feature</th><th>ZooKeeper</th><th>etcd</th></tr></thead><tbody><tr><td>Data model</td><td>Tree of znodes, like a file system</td><td>Flat key space with range queries over prefixes</td></tr><tr><td>Tied to client liveness</td><td>Ephemeral znodes vanish when the client's session ends</td><td>Keys attached to a lease vanish when the lease expires</td></tr><tr><td>Change notification</td><td>Watches on znodes</td><td>Watch on keys or prefixes, from a given revision</td></tr><tr><td>Atomic updates</td><td>Conditional writes on znode version, multi-op</td><td>Transactions: compare, then put or delete</td></tr><tr><td>Ordering</td><td>Sequential znodes get increasing suffixes</td><td>Every change gets a global revision number</td></tr></tbody></table></div>\n<p>These primitives combine into standard recipes:</p>\n<ul>\n<li><strong>Service discovery.</strong> Each instance registers itself under a key tied to its session or lease. If it dies, the key disappears. Clients watch the prefix to keep an up-to-date list. This is the mechanism behind the primer's <a href=\"/learn/application-layer#service-discovery\">service discovery</a> section.</li>\n<li><strong>Configuration.</strong> Store feature flags or settings under a key and have services watch it, so changes reach every instance within seconds and every instance sees changes in the same order.</li>\n<li><strong>Leader election.</strong> In ZooKeeper, each candidate creates an ephemeral sequential znode under <code>/election</code>. The lowest number is leader. Every other candidate watches only the node just below its own, so a leader's death wakes one successor instead of the whole group. etcd's client library offers an election API built on leases and revisions.</li>\n<li><strong>Distributed locks.</strong> The same pattern, with the lock released when the holder's node or lease goes away. Use the znode version or etcd revision as the fencing token.</li>\n</ul>\n<p>Real systems use these heavily. Kubernetes stores all cluster state in etcd. HBase and HDFS high-availability rely on ZooKeeper for master election. Kafka relied on ZooKeeper for cluster metadata for years, then replaced it with its own Raft-based controller (KRaft), and Kafka 4.0 dropped ZooKeeper support entirely.</p>\n<p>Two cautions. Sessions and leases take seconds to expire, so failover isn't instant and an old leader may not learn right away that it lost; fencing covers that gap. And ZooKeeper serves reads from whichever server a client is connected to, which may lag; call <code>sync</code> first when you need the latest value.</p>","headings":[{"id":"zookeeper-and-etcd-in-practice","title":"ZooKeeper and etcd in practice","depth":3}]},{"id":"when-you-dont-need-consensus","title":"When you don't need consensus","html":"<h3 id=\"when-you-dont-need-consensus\">When you don't need consensus</h3>\n<p>Consensus costs extra round trips on every write, a cluster you must operate, and unavailability on the minority side of a partition. Many problems don't need it:</p>\n<ul>\n<li><strong>Something you already use already does it.</strong> A managed database with automatic failover runs its own leader election. You don't need a second one on top.</li>\n<li><strong>The data can merge.</strong> Counters, sets of likes, shopping-cart additions and other commutative updates can be accepted on any replica and combined later, using CRDTs or application-level merges. This is the <a href=\"/learn/consistency-patterns#eventual-consistency\">eventual consistency</a> route, and it stays available during partitions.</li>\n<li><strong>Leaderless quorums are enough.</strong> Dynamo-style stores accept reads and writes from any replica and choose read and write quorums so they overlap (R + W > N). They don't provide a linearizable compare-and-set, but for many workloads that's fine. See <a href=\"/learn/design-key-value-store\">designing a key-value store</a>.</li>\n<li><strong>Duplicate work is harmless.</strong> If a nightly cleanup job occasionally runs twice and the job is idempotent, a simple lock with an expiry in Redis is fine. Reserve consensus-backed locks with fencing for cases where two holders would corrupt data.</li>\n<li><strong>One owner per partition, assigned rarely.</strong> Use a coordination service only to assign shards to owners, and keep the per-request path free of consensus.</li>\n</ul>\n<p>When you do need it, use etcd, ZooKeeper, Consul or your database's built-in replication. Say in the interview which component provides consensus and what happens to your system when that component's minority side is cut off.</p>","headings":[{"id":"when-you-dont-need-consensus","title":"When you don't need consensus","depth":3}]},{"id":"check-yourself","title":"Check yourself","html":"<h3 id=\"check-yourself\">Check yourself</h3>\n<details>\n<summary>Your team runs a 4-node etcd cluster \"for extra safety\". What would you suggest, and why?</summary>\n<p>A 4-node cluster needs 3 votes for a majority, so it tolerates only 1 failure, the same as 3 nodes, while every write waits for one more acknowledgment. Use 3 nodes, or 5 if you need to survive 2 failures. Placing the nodes in different availability zones matters more than adding a fourth node.</p>\n</details>\n<details>\n<summary>A Raft cluster of 5 nodes splits into a group of 2 (including the old leader) and a group of 3. What happens on each side, and what happens when the network heals?</summary>\n<p>The old leader can't reach a majority, so its new writes never commit and clients get errors or timeouts. In the group of 3, a follower times out, wins an election in a higher term, and keeps serving writes. When the network heals, the old leader sees the higher term, steps down, and its uncommitted entries are overwritten by the new leader's log.</p>\n</details>\n<details>\n<summary>A job scheduler uses a lock with a 30-second lease so only one instance runs a billing job that writes invoices to Postgres. Occasionally customers get two invoices. What's the likely cause and fix?</summary>\n<p>An instance probably paused or ran long past its lease, another instance took the lock, and the first one kept writing after waking up. Have the lock service issue a fencing token (an etcd revision or ZooKeeper version), store the highest token per billing run in Postgres, and make each write conditional on its token being the highest seen. Making invoice creation idempotent, for example with a unique key per customer and billing period, adds a second layer of protection.</p>\n</details>\n<details>\n<summary>Why does ZooKeeper's leader election recipe have each candidate watch only the node just before its own, rather than the current leader's node?</summary>\n<p>If every candidate watched the leader's node, its deletion would wake all of them at once, and they would all hit ZooKeeper to re-check the list. With N candidates that is N requests for one change, known as the herd effect. Watching only the predecessor means each change wakes exactly one candidate, which checks whether it is now the lowest.</p>\n</details>\n<details>\n<summary>A teammate wants to put a Raft cluster in front of a \"likes\" counter so the count is always exact. Is that a good idea?</summary>\n<p>Probably not. Every like would pay a consensus round trip, and during a partition the minority side couldn't accept likes at all. Likes are additions that can be merged later, so sharded counters or per-replica counts summed on read give an accurate total eventually, with much lower cost. Exactness to the second rarely matters for a like count.</p>\n</details>\n<h3 id=\"further-reading\">Further reading</h3>\n<ul>\n<li><a href=\"https://raft.github.io/raft.pdf\" target=\"_blank\" rel=\"noopener noreferrer\">Diego Ongaro and John Ousterhout, \"In Search of an Understandable Consensus Algorithm\"</a>: the Raft paper. Readable in an evening.</li>\n<li><a href=\"https://raft.github.io/\" target=\"_blank\" rel=\"noopener noreferrer\">The Raft consensus algorithm</a>: an interactive visualization of elections and replication.</li>\n<li><a href=\"https://lamport.azurewebsites.net/pubs/paxos-simple.pdf\" target=\"_blank\" rel=\"noopener noreferrer\">Leslie Lamport, \"Paxos Made Simple\"</a>.</li>\n<li><a href=\"https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html\" target=\"_blank\" rel=\"noopener noreferrer\">Martin Kleppmann, \"How to do distributed locking\"</a>: leases, process pauses and fencing tokens.</li>\n<li><a href=\"https://zookeeper.apache.org/doc/current/recipes.html\" target=\"_blank\" rel=\"noopener noreferrer\">ZooKeeper recipes and solutions</a>: the official leader election, lock and barrier recipes.</li>\n<li><a href=\"https://etcd.io/docs/\" target=\"_blank\" rel=\"noopener noreferrer\">etcd documentation</a>: leases, watches, transactions and the concurrency API.</li>\n<li>Alex Xu, <em>System Design Interview – An Insider's Guide</em> (2020), \"Design a key-value store\".</li>\n</ul>","headings":[{"id":"check-yourself","title":"Check yourself","depth":3},{"id":"further-reading","title":"Further reading","depth":3}]}],"sourceFiles":[]}