System Design
Advanced 13 min read Reliability, security & operations

Consensus and leader election

In one line

Consensus lets a group of machines agree on one value or one ordered log even when some crash; you rarely build it yourself, but you use it through etcd, ZooKeeper or a Raft-based database, and you must understand quorums, leases and fencing to use it safely.

What it is

Many systems need exactly one of something: one primary database, one scheduler that runs a cron job, one owner of a Kafka partition, one current config. Choosing that one machine and making every other machine agree, even when networks are slow and machines crash, is the consensus problem. Leader election is its most common use.

Consensus algorithms such as Paxos, Raft and ZAB (used by ZooKeeper) work with a majority quorum. A cluster of 2f+1 nodes tolerates f failures: 3 nodes survive 1 failure, 5 survive 2. A value is decided only when a majority accepts it, and any two majorities overlap in at least one node, so two conflicting decisions cannot both win. A minority side of a network partition stops making progress instead of diverging.

In practice you use consensus as a service: etcd (behind Kubernetes), ZooKeeper (classic Kafka, HBase, Hadoop), Consul, or databases with Raft built in such as CockroachDB, TiDB, YugabyteDB and Kafka's KRaft mode. The important skills are knowing what they guarantee, how to elect a leader on top of them with leases, and how to fence a stale leader that does not know it has lost its role.

Why interviewers ask

Interviewers bring this up whenever your design has a 'single primary', a 'scheduler', a 'lock' or 'automatic failover'. They want to hear how the leader is chosen, what happens during a network partition, why you need an odd number of nodes, and how you stop two leaders from writing at once. Saying 'use a Redis lock' without discussing expiry and fencing is a common way to lose points at SDE-3 and staff levels.

How it works

  1. 1Raft splits time into numbered terms. Each node is a follower, a candidate or the leader. Followers expect regular heartbeats from the leader.
  2. 2If a follower hears nothing for its election timeout (randomised, for example 150-300 ms in the Raft paper; often around 1 s in etcd deployments), it increments the term, becomes a candidate, votes for itself and asks others for votes.
  3. 3Each 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 with votes from a majority becomes leader. Random timeouts make split votes rare.
  4. 4The leader accepts client writes, appends them to its log and replicates them to followers. An entry is committed once a majority has stored it; then it is applied to the state machine (for example a key-value map) and the client gets success.
  5. 5If the leader crashes, a new election happens; the new leader is guaranteed to have all committed entries, so no acknowledged write is lost.
  6. 6During a partition, only the side with a majority can elect a leader and commit. An old leader on the minority side may still think it is leader for a short time, but it cannot commit new writes. Reads from it can be stale unless reads also go through a quorum check or a lease.
  7. 7Write latency = time to reach a majority. Across three regions that means one inter-region round trip per write, which is why most consensus clusters live in one region across 3 AZs.
  8. 8To elect a leader for your own service, use the consensus store: create a key with a lease (etcd) or an ephemeral node (ZooKeeper). The holder is leader while it keeps renewing; if it dies, the lease expires and another instance takes over.
  9. 9Fence every leader action. A paused leader (long GC pause, VM freeze) can wake up after its lease expired and keep writing. Give each new leadership a monotonically increasing fencing token (etcd revision, ZooKeeper zxid, Raft term) and make the storage reject writes with a token lower than the highest it has seen.

Worked example · Leader failover in a 5-node Raft cluster

  1. Cluster: nodes A-E across 3 AZs (2, 2, 1). Leader A in term 7. Heartbeats every 100 ms, election timeouts randomised between 1,000 and 2,000 ms.
  2. A client writes 'config v42'. A appends it at log index 1,001 and sends it to B-E. A, B and C store it (3 of 5 = majority), so it is committed. D and E are slow but will catch up.
  3. AZ-1 loses network: A and B are cut off from C, D and E.
  4. D's timer fires first at 1,200 ms. D moves to term 8 and asks for votes. C and E vote for D, but C has entry 1,001 and D does not, so C refuses. E votes yes. D has only 2 votes, not 3. No leader yet.
  5. C's timer fires next, term 9. C's log is the most up to date. D and E vote for C: 3 of 5 votes. C becomes leader with entry 1,001 safely preserved.
  6. Meanwhile A, on the minority side, still believes it leads term 7. It accepts a client write but can reach only B (2 of 5), so the write never commits and the client times out.
  7. The network heals. A sees term 9 in C's messages, steps down, and its uncommitted entry is overwritten by C's log. Total unavailability for writes: about 1.5-2.5 seconds. Committed data lost: none.

Picture it

A housing society committee of five members voting on decisions. A decision is valid only with at least three votes, so even if two members are away, the society can still decide, and two rival groups can never both pass opposite decisions because they would need six members between them. If the chairperson stops attending, members call an election; the new chair gets a fresh term number, and any letter signed with an older term number is ignored by the bank.

Reference code

-- Storage-side fencing: the resource accepts work only from the newest leader.
CREATE TABLE scheduler_fence (
  name          text PRIMARY KEY,
  highest_token bigint NOT NULL
);

-- Every write by a leader first passes through the fence in the same transaction.
BEGIN;
UPDATE scheduler_fence
   SET highest_token = :my_token
 WHERE name = 'campaign-scheduler'
   AND highest_token <= :my_token;   -- 0 rows updated => a newer leader exists, abort

UPDATE campaigns
   SET state = 'enqueued'
 WHERE id = :campaign_id AND state = 'due';
COMMIT;

Key patterns

Lease-based leader election

When: One active instance of a scheduler, compactor or partition owner

How: Acquire a key with a TTL lease in etcd, ZooKeeper or Consul; renew it periodically; on failure to renew, stop work. Others watch the key and take over when it disappears.

Fencing tokens

When: Any leader or lock holder that writes to shared storage

How: Each new leadership gets a higher number; storage remembers the highest seen and rejects writes with lower numbers.

Small consensus core, scalable data plane

When: Large systems

How: Keep only membership, leadership and config in the consensus store (kilobytes to a few GB); put the bulk data in sharded systems whose shards each have a leader chosen by the core.

Odd-sized clusters

When: Sizing a consensus cluster

How: Use 3 or 5 nodes spread across 3 failure domains. A 4th node adds load but no extra fault tolerance; 7+ nodes slow writes.

Real-world use

etcd and Kubernetes

Kubernetes stores all cluster state in etcd, a Raft-based key-value store. Production control planes usually run 3 or 5 etcd members; etcd's default storage quota is 2 GB, because it is designed for small, critical coordination data, not bulk data.

Kafka KRaft

Kafka originally used ZooKeeper for controller election and metadata. KRaft mode replaced it with a Raft-based quorum of controllers inside Kafka, and Kafka 4.0 removed ZooKeeper support.

Google Chubby

Chubby is Google's Paxos-based lock service, used by systems such as Bigtable to elect masters and store small metadata. Its paper introduced sequencers, an early form of fencing tokens.

CockroachDB, TiDB and YugabyteDB

These distributed SQL databases split data into ranges or tablets, each replicated by its own Raft group, with range leases so reads can be served by the leaseholder without a full quorum round trip.

Redlock debate

Martin Kleppmann's analysis of Redis's Redlock algorithm showed that lease-based locks without fencing tokens are unsafe under process pauses and clock jumps, which is why correctness-critical locks use fencing.

In a project

A notifications platform runs a scheduler that every minute picks due campaigns and enqueues millions of push messages. It runs on three pods for availability, and twice last month a campaign was sent twice because two pods both believed they were the active scheduler after a network blip.

  1. Remove the home-made 'leader flag in Postgres with a timestamp' approach that relied on clocks.
  2. Use Kubernetes Lease objects (backed by etcd) or an etcd lease directly for leader election, with a 15 s lease renewed every 5 s.
  3. On becoming leader, read the lease's revision number and use it as a fencing token.
  4. Make the campaign table reject updates from older tokens: UPDATE campaigns SET state='enqueued', leader_token=:t WHERE id=:id AND state='due' AND leader_token < :t.
  5. Make enqueuing idempotent too: each push job has a deterministic ID (campaign_id + user_id), and the queue consumer ignores duplicates.
  6. Have a leader stop work immediately when renewal fails, and alert on leadership changes.

Impact: Double sends stop: even if an old leader wakes from a long pause, the database rejects its writes and duplicate jobs are dropped. Failover to another pod happens within about 15 seconds without manual action.

Trade-offs

Use it when

  • You need exactly one active instance for correctness (scheduler, primary, partition owner)
  • Cluster membership, configuration or metadata must be consistent across nodes
  • Automatic failover where split brain would corrupt data
  • Strongly consistent small data with low write rates

Avoid it when

  • Bulk or high-throughput data: etcd and ZooKeeper are for coordination, not for millions of writes per second
  • Spreading a consensus group across distant regions when write latency matters; each write pays the inter-region round trip
  • Work that can be made idempotent and run by many workers in parallel; you may not need a leader at all
  • Implementing Raft or Paxos yourself for production; use a proven implementation

Common mistakes

  • Running an even number of nodes (2 or 4), which adds cost without adding fault tolerance; a 2-node cluster cannot survive any failure.
  • Using a lock with a TTL but no fencing token, assuming the holder always knows when its lease expired.
  • Electing leaders by comparing wall-clock timestamps across machines.
  • Reading from a Raft follower or an old leader and assuming the data is up to date.
  • Storing large or frequently changing data in etcd or ZooKeeper.
  • Claiming the system stays fully available during a partition: the minority side must stop accepting writes.

Interview questions

Quick revision

  • 2f+1 nodes tolerate f failures: 3 -> 1, 5 -> 2.
  • Majorities overlap, so conflicting decisions cannot both commit.
  • Raft: terms, randomised election timeouts, majority votes, log replication, commit at majority.
  • Minority partition cannot commit; consensus chooses consistency over availability.
  • Write latency = round trip to a majority.
  • Use etcd, ZooKeeper, Consul or a Raft-based DB; do not write your own.
  • Leader election = lease or ephemeral node + watch + renew.
  • Leases need fencing tokens to be safe against pauses.
  • Keep consensus stores small: coordination data only.
  • Kafka 4.0 uses KRaft; ZooKeeper is gone from Kafka.

Check yourself

1. How many node failures can a 5-node Raft cluster tolerate while still committing writes?

2. What is the main purpose of a fencing token?

3. Why does Raft use randomised election timeouts?

4. A 3-node etcd cluster is spread across three regions about 40 ms apart. What happens to write latency?

Saved on this device. Create a free account to keep it on every device.

Practise this with Sam

A real mock interview, out loud, with an honest scorecard. Free to start.

Start free