Distributed Fundamentals: Partial Failure, Quorums, Consensus, Recovery
A distributed database is several machines that each fail independently and cannot tell a slow peer from a dead one; quorums, leader election and consensus exist so that a majority can keep a single history alive through those failures — and every failure has a specific recovery it demands.
Why this exists
The mechanism as the answer to a problem — read this before the name.
- Problem
Data lives on three machines connected by a network. Any machine can crash, pause, or be cut off; any message can be delayed or lost; and no node can distinguish "crashed" from "slow" from "unreachable" — yet clients must get one consistent answer, and the system must keep working when one node is gone.
↓ - Naive solution
One primary accepts writes and forwards them to two replicas. If the primary looks dead, promote a replica. If a replica looks dead, ignore it.
↓ - Why it breaks
"Looks dead" is a timeout. A partition makes the primary look dead to the replicas while it is alive and still serving its own clients; promote a replica and two primaries accept conflicting writes — split brain. Ignore a replica that was merely slow and it later returns with a divergent history. Every decision made from one node's view of the network can be wrong.
↓ - Better idea
Never let one node decide. Require a majority of nodes to agree before a write is committed or a leader is recognised; a majority cannot exist on both sides of a partition at once, so at most one side proceeds. Number the leaderships so an old leader's messages can be rejected. Order every write in a single log that a majority has copied.
↓ - Internal mechanism
Quorums: W + R > N so reads overlap writes. Leader election with monotonically increasing terms; a candidate needs a majority of votes and must hold a log at least as complete as each voter's. A replicated log: the leader appends, followers acknowledge, an entry is committed when a majority holds it. Lamport-style ordering so events on different nodes can be compared.
↓ - Trade-offs
Every commit costs a majority round trip; the minority side of a partition is unavailable for writes (CP), or, if you let it write, you take divergence and must merge (AP). Consensus tolerates f failures with 2f + 1 nodes and no more. Cross-region majorities add tens of milliseconds to every write.
↓ - Real database
etcd, ZooKeeper, Consul (consensus stores); CockroachDB, Spanner, TiDB, YugabyteDB (Raft/Paxos under every range); Cassandra and DynamoDB (quorum reads and writes without a leader); Patroni (consensus lease around a PostgreSQL primary).
Choose your depth
The same mechanism at four altitudes. Start where you are; come back deeper.
On one machine, either the whole process is running or it is not. Across machines, some are up, some are down, some are unreachable, and the ones that are up cannot know which of the others are which — they only know who answered within a timeout. Every rule in distributed databases is a way of acting safely despite that.
The core rule is the majority: with three nodes, two agreeing is enough to proceed and one alone is not, because two disjoint groups of two cannot exist. A write is committed when a majority has it; a leader is a leader when a majority voted for it; a read is current when it asks enough nodes to overlap the last write.
Partial failures and unreliable networks
A single-node database fails all at once: the process is running or it is not, and a crash is detected by the operating system in microseconds. A distributed database fails partially: node B is down while A and C are up; the link between A and B is cut while both are fine; C is paused for 20 seconds by a garbage collector or a VM migration and then resumes as if nothing happened. And the only failure detector available is a timeout — "no reply within 5 s" — which cannot distinguish a dead node from a slow one from a working node behind a broken link. Networks also reorder, duplicate and delay messages, so "I sent the write and got no reply" leaves three possibilities: it was lost, it was applied and the reply was lost, or it is still in flight.
Every mechanism in this lesson is an answer to one question: how can a group of nodes act safely on incomplete and possibly wrong information about each other? The recurring answer is that no single node's opinion is trusted; decisions require a majority, and every decision is numbered so that a node acting on stale information can be recognised and refused.
- Crash-stop: a node halts and stays halted. The easy case; its data survives on its disk.
- Crash-recovery: a node halts and returns later with its old state — possibly with writes nobody else has, possibly missing writes everyone else has.
- Pause: a node stops for seconds (GC, swap, VM stall) and resumes believing no time passed — including believing it still holds a lease or leadership.
- Partition: nodes are up but some pairs cannot communicate. Both sides may believe the other is dead.
- Byzantine: a node lies or is compromised. Out of scope for databases you run yourself; it is why blockchains are slow.
Replica divergence, leader/follower, and the majority rule
Two copies of the data are divergent when each holds acknowledged writes the other lacks. Divergence is not lag — a lagging replica is a prefix of the leader and will converge by replaying; a divergent one has a different suffix and cannot converge without discarding something. It arises whenever two nodes each accept writes for the same data without coordinating: a partitioned old leader and a newly elected one (split brain), two coordinators in a leaderless store with W = 1, or a paused leader that wakes up and serves one more write after its replacement took over.
The leader/follower design prevents divergence by construction — one writer, an ordered log, followers that only apply — as long as there is only ever one leader. The majority rule is what makes "only ever one" enforceable: a node may act as leader, commit a write, or recognise a leader only with the agreement of more than N/2 nodes. Two majorities of the same set always share a member, so two leaders cannot both hold majorities in the same term, and a partition can at most leave one side able to proceed. With N = 3 this tolerates one failure; N = 5 tolerates two; even N buys nothing (4 nodes still tolerate only one, since 2 is not a majority of 4).
Quorums: W + R > N, with numbers
A leaderless replicated store sends every write to all N replicas and calls it successful when W have acknowledged; a read asks R replicas and returns the newest version among their answers. If W + R > N, the set of replicas that acknowledged the last write and the set the read consulted must overlap in at least one replica — so the read sees the latest acknowledged write. With N = 3: W = 2, R = 2 gives the overlap and tolerates one dead node for both reads and writes. W = 3, R = 1 makes reads cheap and writes impossible with any node down. W = 1, R = 1 is fast and tolerates two dead nodes, and a read may miss every replica that has the latest write. The numbers are chosen per operation — Cassandra's QUORUM, ONE, ALL, LOCAL_QUORUM; DynamoDB's consistent-read flag — which is why one system can be CP for the ledger and AP for the shopping cart.
The overlap guarantee is weaker than it looks. It holds among the replicas that *acknowledged*; a replica that was down or slow during the write is not in the overlap set and, if the read picks it and one other, may still see stale data unless W + R > N accounts for it (sloppy quorums and hinted handoff make this worse, not better). Concurrent writes to the same key with W < N can leave replicas holding different "latest" versions with no way to order them — hence version vectors, read repair (the reader writes the newest version back to stale replicas), and anti-entropy (background Merkle-tree comparison). And "newest" needs an ordering, which is where Lamport comes in.
| W | R | W + R > N | Write survives | Read survives | Read sees last ack'd write |
|---|---|---|---|---|---|
| 1 | 1 | no (2) | 2 nodes down | 2 nodes down | no — may be stale |
| 1 | 3 | yes (4) | 2 nodes down | 0 nodes down | yes |
| 2 | 2 | yes (4) | 1 node down | 1 node down | yes |
| 3 | 1 | yes (4) | 0 nodes down | 2 nodes down | yes |
| 2 | 1 | no (3) | 1 node down | 2 nodes down | no — may be stale |
Ordering without clocks
Wall clocks on different machines disagree by milliseconds at best and by seconds after a bad NTP step, so "the write with the later timestamp is newer" is a guess that last-writer-wins systems make and sometimes get wrong. Lamport's observation: what matters is causality, not time. Give every node a counter; increment it before each local event; attach it to every message; on receiving a message, set the counter to max(local, received) + 1. Then if event a could have influenced event b (same node earlier, or a message from a's side to b's), stamp(a) < stamp(b) — a total order consistent with causality, obtained with no clock at all. It cannot tell you that two events were *concurrent* (independent); for that, vector clocks keep one counter per node and compare component-wise: a ≤ b in every component means a happened before b, otherwise they are concurrent and a conflict must be resolved. Raft's (term, index) pair is a Lamport clock specialised to a single log; Spanner's TrueTime is the expensive alternative of making wall clocks trustworthy with GPS and atomic clocks plus an explicit uncertainty interval.
Leader election and consensus
Consensus is the problem of getting N nodes to agree on one value — in practice, on the next entry of a shared log — despite crashes and partitions. Raft solves it in five sentences. Time is divided into numbered terms; every message carries its sender's term, and any node that sees a higher term adopts it and, if it was leader, steps down. A follower that misses heartbeats becomes a candidate, increments the term, and requests votes; a majority makes it leader, and a node refuses to vote for a candidate whose log is less complete than its own (lower last term, or same term and shorter). The leader appends client commands to its log and replicates them with AppendEntries carrying the previous entry's index and term, so a follower with a conflicting suffix rejects, is walked back, and truncates. An entry is committed when the leader has it on a majority; the leader then applies it and advertises the commit index. Because leaders come from the most complete logs and never delete their own entries, a committed entry is in every future leader's log — nothing acknowledged is ever lost while a majority survives.
What you get from this: exactly one leader per term (two majorities share a voter, and a voter votes once per term), no acknowledged write lost across any sequence of failures that leaves a majority, and automatic, safe failover — the property the naive design could not provide. What it costs: a majority round trip per commit (two across regions), 2f + 1 nodes to tolerate f failures, and a leader that must stop serving when it cannot reach a majority (the lease: heartbeat replies prove the majority still recognises it; without them it must assume it has been replaced). Paxos reaches the same guarantees with a different decomposition; Multi-Paxos in practice looks like Raft. Every strongly consistent distributed database is one of these protocols with a storage engine as the state machine — CockroachDB runs a Raft group per 512 MB range; etcd is one Raft group and a key-value store; Patroni runs no Raft itself but rents a leader lease from one.
index: 1 2 3 4 5 6
A (leader) t1 t1 t2 t2 t2 t2 commitIndex = 5 (entries 1–5 on a majority: A and B)
B t1 t1 t2 t2 t2 matches A through 5; 6 in flight
C t1 t1 t1' t1' C appended 3',4' alone in term 1 during the partition: never committed
heal: A → C AppendEntries(prevIndex=5, prevTerm=t2, …) → C: "no entry (5, t2)" → reject
A → C AppendEntries(prevIndex=2, prevTerm=t1, entries 3..6) → C: prefix matches → truncate 3',4' → append 3..6
result: C's 3',4' discarded (they were acknowledged to no client: W never reached a majority)
every node: t1 t1 t2 t2 t2 t2 — one historyWhat recovery each failure requires
The failure simulator below lets you produce each of these. The table is the answer key: for each failure, what survives, what becomes unavailable, whether stale reads occur, and what must happen before the cluster is whole again. Notice that in a leader-based CP system every recovery is one of two operations — replay (a prefix catches up) or truncate then replay (a divergent suffix is discarded, then the node catches up) — and that the only writes ever discarded are ones the protocol never acknowledged, *unless* fencing was missing. In a leaderless AP system there is no truncation; divergence is merged, and last-writer-wins merges by losing.
| Failure | Survives | Unavailable | Stale reads? | Recovery required |
|---|---|---|---|---|
| Follower crashes | everything (leader + other follower) | reads routed to it | no | restart; replay from its last index |
| Follower delayed | everything | nothing | yes, on that follower | un-delay; replay the backlog; never elect it while behind |
| Leader crashes (async) | all entries a majority holds | writes until election | no | elect from the most complete log; unreplicated tail lost; old leader truncates on restart |
| Leader crashes (sync / majority commit) | every acknowledged write | writes until election | no | elect; no loss; old leader truncates its uncommitted tail |
| Partition, minority holds old leader, lease honoured | majority side continues | minority side entirely | on minority if it serves reads | heal; minority steps down; truncate its unacked tail; replay |
| Partition, old leader unfenced (split brain) | both sides accept writes | nothing — that is the problem | both sides, relative to each other | heal; lower term steps down; its ACKNOWLEDGED tail discarded or hand-merged |
| Two of three nodes down | the surviving node's data | all writes; reads if quorum required | possibly | restore a second node; re-form a majority; no election until then |
| Leaderless, W = R = 1, partition | both sides accept | nothing | yes | anti-entropy; conflicts resolved by version vector or LWW (data loss) |
Key points
- Distributed failure is partial, and the only detector is a timeout: slow, dead and partitioned look identical from any one node.
- Divergence is not lag: a lagging replica is a prefix and replays; a divergent one has a different suffix and must truncate or merge.
- The majority rule — writes, votes and leadership need > N/2 — is what makes "one leader" and "one history" enforceable across a partition. 2f + 1 nodes tolerate f failures.
- W + R > N makes every read overlap the last acknowledged write, among the replicas that acknowledged; the numbers are chosen per operation.
- Lamport clocks order events by causality without wall clocks; vector clocks additionally detect concurrency. Raft's (term, index) is one.
- Raft: numbered terms, majority election from the most complete log, leader-appended log verified by (prevIndex, prevTerm), commit at majority. Recovery is replay, or truncate-then-replay; only unacknowledged entries are ever discarded — unless fencing was missing.
Replication failure simulator
When to use — and when not
- This design fits when the data must survive any single node loss with zero acknowledged-write loss and automatic failover, and commits can afford a majority round trip.
- Quorum tuning per operation fits when different parts of the workload need different consistency.
- This design fits poorly when one node with replicas would serve the load: consensus costs latency, operational complexity and 2f + 1 machines for every f failures tolerated.
- Running a majority across continents for latency-sensitive writes; place the majority in one region and replicate outward.
Failure modes
- Promotion without fencing: the old primary keeps writing, split brain, acknowledged writes discarded.
- Electing a delayed replica because it answered first: everything it had not replayed is lost.
- Even node counts or two-node clusters: no majority is possible after one failure, so no election ever succeeds.
- Last-writer-wins on wall-clock timestamps silently dropping the causally later write.
- A paused leader waking after its lease expired and serving one more write.
Where you meet this
Back up to the practical layer, and across to the rest of Engineer Atlas.
- Distributed SystemsConsensus (Raft / Paxos) and state machine replication → Distributed databases: a consensus log with a storage engine applying itCockroachDB, Spanner, TiDB and etcd differ in the state machine, not in the agreement protocol.
- DSAPartial order / DAG of causally related events → Lamport and vector clocks ordering writes across replicas
- NetworkingTimeouts, retries and the two-generals problem → Failure detection is a timeout; a partition is indistinguishable from a crash