Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path →
Consensus: nodes crash,
the cluster still agrees.
Machines die, messages vanish, clocks drift — and yet a distributed system must still settle on exactly one answer: one leader, one next log entry, one commit decision. That's the consensus problem, the hardest in the field, and the algorithms that solve it underpin almost everything reliable.
Why agreement is hard
Consensus means getting a group of nodes to agree on a single value, even when some fail. It sounds trivial until you add reality: messages arrive late or never, nodes crash and recover, and you can't tell a slow node from a dead one. The FLP impossibility result (Fischer, Lynch and Paterson, 1985) proves that in a fully asynchronous network, no deterministic algorithm can guarantee it will ever reach a decision if even one node may crash. So real systems split the promise: they are always safe, never deciding two different values, and they use timeouts and randomization to make progress whenever the network behaves well enough.
Many everyday problems are consensus in disguise: electing a leader, agreeing the next entry in a replicated log, deciding whether a distributed transaction committed, or maintaining a distributed lock. Solve consensus once and you get all of them.
Quorums: agreement by majority
The core trick is the majority quorum. If every decision needs votes from a strict majority, then any two decisions must share at least one node — because two majorities of the same set always overlap. That single overlapping node can't vote for two conflicting values, so two leaders or two conflicting commits can never both succeed, even during a partition.
It's all counting. A majority is ⌊N/2⌋+1, a cluster survives N−majority failures, and because two majorities sum to more than N they must share a node — so at most one leader per term:
And 3+3 > 5, so two candidates can't both reach a majority — Raft's single-leader guarantee, from pure arithmetic. The runnable version below runs an election and the fault-tolerance table.
These majority quorums are not the tunable W and R quorums of leaderless stores. A W + R > N quorum guarantees that two sets of replicas overlap, but it never gets nodes to agree on an order of operations, which is why it can't give you a safe compare-and-set. The CAP handbook works through where those quorums break.
Raft & Paxos
The two famous consensus algorithms. Same guarantee, different ergonomics.
Raft was designed to be understandable. It elects a single leader for a term; the leader takes all writes, appends them to a replicated log, and an entry is committed once a majority has stored it. If the leader goes silent, followers time out and a new election starts. It powers etcd, Consul, and CockroachDB.
times out
requests votes
→ Leader
to followers
Paxos reaches the same safety with proposers, acceptors, and learners, but is famously hard to reason about and implement; Multi-Paxos adds a stable leader for efficiency. The first rule is to reach for an existing implementation rather than writing your own. The internals below still matter: they are what an interviewer probes with "what happens if the leader crashes mid-write?", and they explain how the systems you run fail.
Inside Raft: terms, votes and the log
Raft is safe because of a few rules, and each one closes a specific hole.
Terms are Raft's logical clock. Each term starts with an election and has at most one leader, and every message carries the sender's term. A node that sees a higher term adopts it and becomes a follower; a message with a lower term is rejected. That one rule is how a deposed leader finds out.
Randomized election timeouts stop elections colliding. A follower that hears nothing from a leader for a random interval (the Raft paper suggests 150–300 ms) starts an election. Usually one node times out first, wins, and sends heartbeats before anyone else wakes; if two candidates split the vote, both time out again with fresh random waits. The timeout must sit well above the network round trip and well below the time between failures.
The vote rule keeps committed entries alive across leader changes. A node votes at most once per term, and only for a candidate whose log is at least as up to date as its own: the later last-entry term wins, and on a tie the longer log wins. A committed entry sits on a majority and a winner needs votes from a majority, so some voter holds every committed entry and refuses any candidate missing one. Every new leader therefore already has every committed entry.
# RequestVote: at most one vote per term, only for an up-to-date log
def grant_vote(me, term, cand, cand_last_term, cand_last_index):
if term < me.term:
return False # stale candidate
if term > me.term:
me.term, me.voted_for = term, None # new term: become follower, forget old vote
up_to_date = (cand_last_term, cand_last_index) >= (me.last_term(), me.last_index())
if me.voted_for in (None, cand) and up_to_date:
me.voted_for = cand # persist to disk before replying
return True
return False
# AppendEntries: accept only if our log matches the leader's just before the new entries
def log_matches(me, prev_index, prev_term):
return prev_index == 0 or me.term_at(prev_index) == prev_term
Log matching is how the leader repairs followers. Each AppendEntries message carries the index and term of the entry just before the new ones, and a follower rejects it unless its own log has that entry. On a rejection the leader retries from an earlier entry until the logs agree; the follower then deletes everything after that point and takes the leader's entries. By induction, if two logs hold an entry with the same index and term, they are identical up to that entry.
The commit rule has one subtlety, the case the Raft paper devotes a figure to. An entry is committed once the leader that created it has stored it on a majority, and a leader advances its commit index only by counting replicas of entries from its own term. An entry left over from an earlier term can sit on a majority and still be overwritten by a later leader; it is safe only once a current-term entry after it commits. So a new leader opens its term by appending a no-op entry: committing it commits everything before it and tells the leader where the commit point is. Watch it happen in the Raft simulator, or code the vote count in the Raft election challenge.
Reads that don't lie: ReadIndex and leases
Raft's writes are safe by construction: a deposed leader can't get a majority to accept its entries. Its reads are not. A leader cut off by a partition, with a new leader already elected on the other side, still has its local state and can answer from it, missing writes the new leader has committed. It hasn't heard of the new term, so from where it sits nothing is wrong.
| Read path | How | Linearizable? | Cost |
|---|---|---|---|
| Local read | The leader answers from local state | No | None |
| Through the log | Append the read as an entry and answer once it commits | Yes | A replication round plus a disk write |
| ReadIndex | Note the commit index, confirm leadership with one heartbeat round to a majority, wait until that index is applied, then answer | Yes | One round trip, no disk write |
| Lease read | After a successful heartbeat round, treat leadership as safe for a bit less than the election timeout and answer locally | Only if clock drift stays bounded | None, but correctness rests on clocks |
| Follower read | The follower asks the leader for a read index, waits until it has applied that far, then answers | Yes | One round trip; spreads the load |
ReadIndex has one precondition: a new leader must first commit an entry from its own term (the no-op above) before it knows the true commit index. In practice, etcd serves linearizable reads by default using ReadIndex and offers a cheaper serializable read answered by the local member, which can be stale. ZooKeeper answers reads from whichever server the client is connected to, so they can lag; calling sync first makes that server catch up with the leader. CockroachDB serves reads from each range's leaseholder.
Changing the cluster: membership and snapshots
Membership changes. Switching from configuration A to configuration B in one step is unsafe. Mid-switch, some nodes count votes against A and some against B, and a majority of each can be disjoint (two of {1, 2, 3} against three of {1, 2, 3, 4, 5}), so two leaders can win the same term. Raft has two safe methods. Joint consensus, from the original paper, passes through an intermediate configuration in which every decision needs majorities of both A and B. Single-server changes, from Diego Ongaro's dissertation, add or remove one node at a time, because then any majority of the old configuration overlaps any majority of the new one. Either way, add a node as a non-voting learner first and promote it once it has caught up, as etcd supports; an empty voter raises the quorum size before it can acknowledge anything.
Snapshots. The log can't grow forever. Each node periodically snapshots its state machine and discards the log up to that point, keeping the index and term of the last entry the snapshot covers, because the next AppendEntries consistency check compares against exactly that entry. A follower so far behind that the leader has already discarded the entries it needs is sent the snapshot itself (InstallSnapshot), then resumes normal replication.
Paxos in two phases
Basic Paxos decides one value. Proposers use proposal numbers that are unique and increasing (a counter paired with the proposer's ID works), and a value is chosen once a majority of acceptors accept the same proposal.
| Step | Message | Rule |
|---|---|---|
| 1a | Prepare(n), proposer to acceptors | Choose n higher than any number the proposer has seen |
| 1b | Promise(n), acceptor to proposer | Only if n beats every Prepare it has answered: promise to reject proposals numbered below n, and report the highest-numbered proposal already accepted, if any |
| 2a | Accept(n, v), proposer to acceptors | Needs promises from a majority. v must be the value of the highest-numbered proposal they reported; only if none was reported may the proposer use its own |
| 2b | Accepted(n, v), acceptor to learners | Accept unless a higher-numbered Prepare has been promised since; once a majority accepts, v is chosen |
Rule 2a is the heart of it: once a value might have been chosen, any later proposer is forced to propose that same value, so a different one can never win. Two proposers can still livelock by pre-empting each other with ever-higher numbers, so real systems elect a distinguished leader. Multi-Paxos runs phase 1 once for that leader, then decides a long run of log slots with phase 2 alone, one round trip each. Google's Chubby lock service and Spanner replicate with Paxos; ZooKeeper uses its own protocol, Zab.
Seen this way, Raft is Multi-Paxos with a stricter rule about who may lead. A term plays the proposal number, an election plays phase 1, and AppendEntries plays phase 2. The difference is that a Paxos leader learns which values it must re-propose from the promises it collects, while Raft lets only a node with an up-to-date log win, so its leader never has to learn anything from voters. Heidi Howard and Richard Mortier's 2020 paper "Paxos vs Raft: Have we reached consensus on distributed consensus?" draws the comparison in detail, and the Raft vs Paxos handbook covers the practical choice.
Coordination services
You almost never implement consensus yourself. Instead you lean on a coordination service — ZooKeeper, etcd, or a cloud equivalent — which runs the consensus protocol internally and exposes simple primitives: leader election, distributed locks, configuration storage, and membership/failure detection. The rule of thumb: keep the coordination service for the small, critical state that must be globally agreed, and keep your bulk data out of it.
The senior move is knowing where you genuinely need consensus (one leader, one source of truth) versus where you can avoid it entirely with idempotency and eventual consistency. Consensus is correct but expensive — every decision costs a majority round-trip.
Distributed transactions: 2PC vs saga
When one logical operation spans multiple services or shards, you need them to agree on commit-or-abort.
Two-Phase Commit (2PC)
- Coordinator: "prepare?" → all vote
- All yes → "commit"; any no → "abort"
- Strong atomicity across services
- Blocks if coordinator dies mid-flight
- Holds locks for the whole round-trip
Saga
- A sequence of local transactions
- Each step has a compensating undo
- Failure → run compensations backwards
- Non-blocking, eventually consistent
- You design the rollbacks
2PC buys you atomicity at the cost of availability — a stalled coordinator leaves participants holding locks. Sagas trade atomicity for availability: each step commits independently and a failure triggers compensating actions (refund the charge, release the seat). Most modern microservice systems prefer sagas precisely because they don't block.
Idempotency: the exactly-once illusion
Networks deliver at least once — retries mean the same request can arrive twice. True "exactly-once delivery" is impossible, but you can get exactly-once effects by making operations idempotent: applying them twice has the same result as applying them once.
The standard mechanism is an idempotency key — a unique ID the client attaches to a request. The server records processed keys and, on a duplicate, returns the original result instead of doing the work again. This is how a retried payment charges the card once, and how a redelivered queue message isn't processed twice.
key=ab12
store ab12
key=ab12
return original
Idempotency is how you avoid needing consensus. If every operation is safe to retry, much of a system can run on cheap at-least-once delivery and eventual consistency — reserving expensive agreement for the few places that truly need one source of truth. Pair it with the fencing tokens from concurrency control to stay correct under retries and pauses.
Count a quorum and run an election
Consensus is majority arithmetic. This computes the fault-tolerance table — and shows why clusters are odd-sized, because a 4-node cluster survives no more failures than a 3-node one, just wasting a vote. Then it runs a Raft leader election: a 3-2 split elects the candidate with the majority, but a 2-2 split (one node crashed) elects no one, forcing a new term. And because two majorities of five sum to six, two candidates can never both win. Change the cluster size or the vote split.
Quick answers
Why do we need Raft or Paxos?
Nodes that can crash or be partitioned still need to agree on one value — the leader, the next log entry, a commit decision. These algorithms let a majority quorum agree safely even when a minority fails.
2PC vs saga?
2PC is an atomic prepare-then-commit across services, but blocks if the coordinator fails. A saga is a chain of local transactions with compensating undos — non-blocking and eventually consistent, but you write the rollbacks.
What is idempotency?
An operation that has the same effect applied once or many times. Using an idempotency key the server deduplicates on, retries give exactly-once effects on top of at-least-once delivery.
Why does consensus need a majority?
Any two majorities of the same set overlap on at least one node, so two conflicting decisions can't both win — even during a partition. That quorum intersection is what keeps consensus safe.
How does Raft elect a leader?
A follower that hears nothing from a leader for a randomized election timeout becomes a candidate, increments its term, votes for itself and asks the others for votes. Each node grants one vote per term, and only to a candidate whose log is at least as up to date as its own. A candidate with votes from a majority becomes leader for that term; a split vote times out and retries in a higher term.
Are reads from a Raft leader linearizable?
Not automatically. A leader that has been deposed without knowing it can still answer from its local state. Linearizable reads need a check: ReadIndex confirms leadership with one heartbeat round to a majority before answering, and lease reads skip that round trip by trusting bounded clock drift. etcd uses ReadIndex for its default linearizable reads.
Two-Phase Commit: How Databases Agree (and Get Stuck)
Explore the topic
See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.
More Handbooks
- The System Design Fundamentals HandbookThe load-bearing ideas behind every distributed system — the CAP theorem and consistency models, concurrency and locking, partitioning and replication, and consensus and coordination — each tied to a real worked design you can study interactively.Read →
- CAP Theorem & Consistency ModelsWhat a distributed system can promise when the network splits — CP vs AP, why "CA" is a myth, PACELC, and the full spectrum from linearizable to eventual consistency. Part of System Design Fundamentals.Read →
- Partitioning, Sharding & ReplicationHow one dataset becomes many — range vs hash partitioning, consistent hashing and virtual nodes, hot partitions, replication topologies, replication lag, and quorums. Part of System Design Fundamentals.Read →
- The Kubernetes HandbookThe one idea under all the YAML — declare desired state, and a control loop makes reality match. Covers the orchestration problem, pods, deployments and replicasets, services and networking, the reconciliation loop and self-healing, the scheduler, config/secrets and health probes, autoscaling, and when you actually need Kubernetes.Read →
- The Observability HandbookSeeing inside production — monitoring vs observability, the three pillars (metrics, logs, traces) and what each answers, structured logging, metric types and the cardinality trap, distributed tracing, the golden signals and SLIs/SLOs/error budgets, alerting on symptoms not causes, and correlating all three during an incident.Read →
- The Networking HandbookHow a request actually travels — the layered model, IP addressing and routing, TCP vs UDP, the TCP handshake and head-of-line blocking, DNS resolution and caching, HTTP/1.1 vs HTTP/2 vs HTTP/3 (QUIC), TLS/HTTPS, and why round-trip latency, not bandwidth, dominates.Read →