Course · 08

How do many machines agree when the network can fail?

Once data lives on more than one machine, messages get lost, clocks disagree and machines pause. This page explains what you can and cannot promise, and the tools that make machines agree: quorums, Raft, fencing tokens, logical clocks and careful retries.

TL;DR

In a distributed system you cannot trust the network, the clock or a silent machine, so you agree by majority vote and number everything.

  • CAP only matters during a network partition: then you either refuse requests (CP) or answer with maybe-old data (AP).
  • Raft picks one leader per term by majority vote; an entry is safe once a majority has it.
  • Locks need fencing tokens, clocks need logical counters or bounded error (TrueTime), and 2PC blocks if its coordinator dies.
  • "Exactly once" really means at-least-once delivery plus idempotent processing. Retry with exponential backoff and jitter.
150–300ms
Example Raft election timeout range (Raft paper)
2of 5
Failures a 5-member etcd cluster survives (etcd FAQ)
~4ms
Typical Spanner clock uncertainty ε (Spanner paper)

—Words you need

Each jargon word once, in plain words glossary

Network partition
Machines are alive but some cannot reach others.
Linearizable
Acts like one copy; once a write finishes, everyone sees it.
Eventual consistency
Copies agree in the end, if writes stop for a while.
Consensus
Many machines agree on one value, even if some fail.
Term (epoch)
A numbered leadership period. Bigger number wins.
Majority quorum
More than half the voters, like 3 of 5.
Fencing token
A rising number given with a lock; old numbers get rejected.
Clock skew
Two machines' clocks show different times.
Logical clock
A counter that orders events without real time.
Idempotent
Doing it twice has the same effect as doing it once.
Two-phase commit (2PC)
Ask everyone "ready?", then tell everyone "commit".
Jitter
Random extra wait, so retries don't all fire together.

Why it is hard

01The eight fallacies of distributed computing

Wrong beliefs that break real systems core

Peter Deutsch wrote seven of these at Sun around 1994. James Gosling added the eighth. Each one is a thing new engineers assume, and production disproves.

1
The network is reliable
Packets drop. Use timeouts, retries and idempotent requests.
2
Latency is zero
A call across regions takes tens of ms. Batch, cache, avoid chatty calls.
3
Bandwidth is infinite
Big payloads clog links. Compress and paginate.
4
The network is secure
Encrypt in transit (TLS) and authenticate every call.
5
Topology doesn't change
Servers come and go. Use service discovery, not fixed IPs.
6
There is one administrator
Many teams and vendors own parts. Expect mixed configs.
7
Transport cost is zero
Serialising and cross-zone traffic cost CPU and money.
8
The network is homogeneous
Different hardware, OSes and protocols. Stick to standard formats.
In everyday words
Sending letters by post: some get lost, some arrive late, some arrive twice, the postman changes, and you can never tell if no reply means "no" or "letter lost".
Actually
There is no surviving Sun page with the list. The usual references are secondary: Wikipedia and Rotem-Gal-Oz's paper "Fallacies of Distributed Computing Explained". The key fact under all eight: a sender who gets no reply cannot tell whether the request was lost, the server crashed, the server is slow, or the reply was lost.

Silence is not an answer. Every design on this page exists because "no reply" has four possible meanings.

Source: Fallacies of distributed computing (secondary) · Rotem-Gal-Oz

02CAP, stated correctly, and PACELC

What you give up when the network splits interview favourite

CAP says: when a network partition happens (machines alive, but cut off from each other), a system must pick. Either it stays consistent (C, every read sees the latest write) and refuses some requests. Or it stays available (A, every working node answers) and may return old data.

The CAP choice during a partition Client A writes x=2 to node 1. The copy to node 2 is blocked by a partition. Client B reads x from node 2, which must either refuse (CP) or answer the old value x=1 (AP). PARTITION WRITE X=2 READ X COPY Client Awriter Client Breader Node 1has x=2 Node 2still x=1 CP: refuseerror or wait AP: answerold x=1 LEGEND Node that must choose Blocked copy Read
Node 2 cannot know about x=2. It must either refuse or answer with x=1.

Common misreadings:

  • "Pick 2 of 3." Wrong. Partitions are not optional, so you never "pick CA" for a multi-machine system. The choice is C or A during a partition.
  • "AP means never consistent." Wrong. With no partition, an AP system usually is consistent within milliseconds.
  • CAP's C is linearizability, not the C in ACID (which means "rules like foreign keys hold").
  • CAP's A is strict: every non-failing node must answer. A system that answers from the majority side only is "not available" in CAP terms, yet very usable.

PACELC (Daniel Abadi, 2012) adds the everyday case: if Partition, choose A or C; Else, choose Latency or Consistency. Even with a healthy network, waiting for replicas costs time.

System (default setup)PACELC classMeaning
Dynamo, Cassandra, RiakPA/ELStay up in a partition; prefer speed normally
VoltDB/H-Store, Megastore, BigTable, HBasePC/ECAlways pay for consistency
MongoDBPA/ECAs classified by Abadi in 2012
Yahoo PNUTSPC/ELFast normally, stricter in a partition
In everyday words
Two bank branches lose their phone line. A customer asks branch 2 for their balance. Branch 2 can say "sorry, come back later" (consistent) or read out yesterday's number (available).
Actually
Gilbert and Lynch's 2002 proof defines C as "Atomic, or linearizable, consistency" and A as "every request received by a non-failing node in the system must result in a response". Brewer himself wrote in 2012: "The '2 of 3' formulation was always misleading because it tended to oversimplify the tensions among properties." Abadi: "The default versions of Dynamo, Cassandra, and Riak are PA/EL systems" (Abadi 2012).

Say it like this in interviews: "During a partition we choose; normally we trade latency for consistency." Then name which side your design picks and why.

Source: Gilbert & Lynch 2002 · Brewer 2012 · Abadi PACELC

03Consistency models on a timeline

Strong, sequential, causal, eventual, read-your-writes core

A consistency model is a promise about which values a read may return. Picture each operation as a bar from when it starts to when it finishes.

Which reads are allowed under linearizability Client A writes x=1. Client C reads while the write is still running, so it may see 0 or 1. Client B reads after the write finished and sees 0, which linearizability forbids but eventual consistency allows. WRITE DONE Client A Client B Client C write x = 1 read gives 0 0 or 1: ok TIME LEGEND Operation, start to end Forbidden if linearizable
Once a write has finished, a linearizable system never shows the old value again, to anyone.
ModelPromiseWorks during a partition?Example
Linearizable (strong)Acts like one copy, in real-time orderNoetcd, ZooKeeper writes, Spanner
SequentialOne order for all, matching each client's own order, but not real timeNoRare as a product label
CausalIf B depends on A, everyone sees A before BYes, if clients stick to one serverMongoDB causal sessions
Read-your-writesYou see your own writesYes, if clients stick to one serverSession routing to leader
Monotonic readsYour reads never go back in timeYesPin user to one replica
EventualCopies agree in the endYesDNS, Cassandra at ONE
In everyday words
Linearizable is a single whiteboard everyone looks at. Causal is a group chat where replies never show before the message they answer. Eventual is gossip: everyone hears the news, just not at the same moment.
Actually
Jepsen defines linearizable as "Every operation appears to take place atomically, in some order, consistent with the real-time ordering of those operations". Causal: "Causally-related operations should appear in the same order on all processes". Monotonic reads: "reads cannot go backwards". And the availability rule: "All models at or stronger than read your writes can be at most sticky available" (Jepsen consistency models).

Stronger models are easier to program and cost latency and availability. Pick the weakest model your users won't notice.

Source: jepsen.io/consistency/models and its pages for linearizable, sequential, causal, read-your-writes, monotonic reads

Agreeing

04Consensus, and how Raft elects a leader

One leader per term, chosen by majority deep dive

Consensus means a group of machines agrees on one value, such as "who is leader" or "what is log entry 7", even if some crash. You need it for leader election, distributed locks and configuration. Heartbeats alone cannot do it safely.

Raft splits time into numbered terms. Each term has at most one leader. A follower that hears nothing for a random election timeout becomes a candidate, adds 1 to the term, votes for itself and asks the others.

Raft leader election in term 5 Server 1 times out, becomes a candidate for term 5 and asks servers 2 and 3 for votes. Both say yes, giving it a majority, so it becomes leader and sends heartbeats. VOTE FOR ME? T5 VOTE FOR ME? T5 YES YES I AM LEADER, T5 Server 1timed out Server 2follower Server 3follower LEGEND Vote request Vote Heartbeat as leader
Server 1 votes for itself, gets 2 more: 3 of 3. It needed only 2.
  • One vote per term. Each server votes for at most one candidate per term, first come first served. So two leaders in the same term is impossible.
  • Random timeouts make split votes rare: usually one server times out first and wins before others start.
  • Up-to-date check. A server refuses to vote for a candidate whose log is behind its own. So a new leader always has every committed entry.
  • Higher term wins. An old leader that sees a higher term steps down at once.
In everyday words
A class picks a monitor. Whoever notices first that there is no monitor stands up and asks for votes. Each kid votes once per round. Win more than half and you are monitor until someone stops hearing from you.
Actually
From the Raft paper: "terms act as a logical clock"; "a candidate must receive votes from a majority of the servers in the full cluster"; "Each server will vote for at most one candidate in a given term, on a first-come-first-served basis"; the example "election timeout is randomized to be between 150ms and 300ms"; and votes are given only if "the candidate's log is at least as up-to-date as the receiver's log". Election Safety: "at most one leader can be elected in a given term" (Raft paper). Google's SRE book: leader election "cannot be solved correctly by using heartbeats" alone.

Majority votes plus one vote per term make two leaders in one term impossible. That is the whole trick behind split-brain safety.

Source: Ongaro & Ousterhout 2014 · raft.github.io · Google SRE book, Managing Critical State

05Raft log replication, and Paxos in one paragraph

An entry is safe once a majority has it deep dive

The leader adds each client command to its log and sends it to followers (the AppendEntries message, which also works as the heartbeat). Once a majority has stored the entry, it is committed: it will never be lost. Then every server applies it to its state machine (for example, its key-value map).

Raft logs and the commit point The leader and follower 1 both have entries 1 to 5. Follower 2 has only entries 1 to 3. Entry 5 is on 2 of 3 servers, a majority, so entries up to 5 are committed. LOG INDEX12345 Leader Follower 1 Follower 2 T1 T1 T2 T3 T3 T1 T1 T2 T3 T3 T1 T1 T2 COMMIT POINT Entry 5 is on 2 of 3 servers. Majority: safe. Catches up later LEGEND Entry (T = term it was made in) Newest committed Missing
Follower 2 is slow, but the leader only needs 2 of 3 copies to commit.
Cluster sizeMajority neededFailures survived
3 servers21
4 servers31
5 servers32
7 servers43

Paxos in one paragraph. Paxos (Lamport) is the older consensus algorithm, with three roles: proposers, acceptors and learners. Phase 1: a proposer picks a number n and sends "prepare n"; acceptors promise to ignore lower numbers and report any value they already accepted. Phase 2: if a majority promised, the proposer sends "accept n, v", using the highest-numbered value already accepted if there is one. A value accepted by a majority is chosen. Raft gives the same safety but was designed to be easier to understand and build; Multi-Paxos plus a stable leader looks a lot like Raft.

In everyday words
A secretary writes minutes and reads each line to the committee. Once more than half have written the line in their notebooks, it is official, even if the secretary faints right after.
Actually
Raft: "A log entry is committed once the leader that created the entry has replicated it on a majority of the servers". Log Matching: "if two logs contain an entry with the same index and term, then the logs are identical in all entries up through the given index". Paxos Made Simple: "three classes of agents: proposers, acceptors, and learners". The etcd FAQ notes that "adding a member to bring the size of cluster up to an even number doesn't buy additional fault tolerance" (etcd FAQ).

Use 3 or 5 voters. 4 costs more than 3 and survives the same single failure.

Source: Raft paper · Paxos Made Simple · etcd FAQ

06Leader election in practice, locks and fencing tokens

Don't build consensus; borrow it interview favourite

You almost never write Raft yourself. You use a service that runs it: etcd (Raft, used by Kubernetes) or ZooKeeper (its own protocol, ZAB). Your app gets a lease or an ephemeral node, and whoever holds it is leader.

  • ZooKeeper recipe: each candidate creates an ephemeral, sequential node (it vanishes when the session dies, and gets a growing number). Lowest number is leader. Each other node watches only the one just below it, so a leader death wakes one node, not all.
  • etcd: the Go concurrency package offers locks and elections built on a lease that the client keeps alive.

But a lock alone is not safe. A client can pause (garbage collection, a slow disk) after getting the lock. Its lease expires, another client gets the lock, and then the first one wakes up and writes anyway.

Fencing tokens stop a paused client Client 1 gets the lock with token 33 and then pauses. Its lease expires and client 2 gets token 34 and writes. When client 1 wakes and writes with token 33, storage rejects it because it has already seen 34. GET LOCK TOKEN 33 GET LOCK TOKEN 34 WRITE, 34 WRITE, 33: REJECTED paused Client 1GC pause Lock serviceetcd Client 2new holder Storagemax seen: 34 LEGEND Request Reply Stale write, stopped
The storage checks the number, not the clock. Old numbers lose.
In everyday words
A deli ticket machine. Number 33 dozed off; the counter already served 34. When 33 wakes up and shouts, the clerk says "we're past you" and ignores it.
Actually
Martin Kleppmann (2016): a fencing token is "simply a number that increases (e.g. incremented by the lock service) every time a client acquires the lock", and the storage rejects any write with a lower token than one it has already processed. ZooKeeper: "use the SEQUENCE|EPHEMERAL flags", and "To avoid the herd effect, it is sufficient to watch for the next znode down". etcd's concurrency package "implements concurrency operations on top of etcd such as distributed locks, barriers, and elections" (Kleppmann, ZooKeeper recipes, etcd concurrency).

A lock is a hint, not a guarantee. The resource you protect must check a fencing token, or a paused client will corrupt it.

Source: How to do distributed locking · ZooKeeper recipes · etcd FAQ

Time

07Clocks: why "which came first?" is hard

Skew, NTP, Lamport, vector, hybrid clocks, TrueTime deep dive

Every machine has its own quartz clock, and they drift apart. NTP (Network Time Protocol) pulls them back toward a time server, but only to within some error. So "write A has timestamp 10:00:00.005, write B has 10:00:00.003" does not prove B happened first.

The fix is to order events by cause, not by wall time. Lamport's rule: keep a counter. Add 1 before each event. Send the counter with each message. On receive, set counter = max(mine, message) + 1.

Lamport clocks on three processes Process 1 sends a message at counter 1; process 2 receives it and sets its counter to 2. Process 2 sends at 3; process 3, whose counter was 1, receives and sets max of 1 and 3 plus 1, which is 4. P1 P2 P3 SENT AT 1 SENT AT 3 1 2 2 3 1 4 LEGEND Event with its counter Receive: max(1, 3) + 1 = 4
Time runs left to right on each process. A receive always gets a bigger number than its send.
ClockWhat it storesWhat it can tell youUsed in
Physical (wall) clock + NTPReal time, with errorRough time. Not safe for ordering close events.Logs, TTLs, last-write-wins
Lamport clockOne counterIf A caused B, then L(A) < L(B). The reverse is not true: a smaller number does not prove a cause.Total ordering, tie-break by node id
Vector clockOne counter per nodeWhether A caused B, B caused A, or they were concurrent (a conflict)Dynamo, Riak versions
Hybrid logical clock (HLC)Physical time + small counterCausal order, and stays close to real timeCockroachDB, YugabyteDB
TrueTimeAn interval [earliest, latest]Real order, if you wait out the uncertaintyGoogle Spanner
In everyday words
Two friends' watches are a few minutes apart, so their photo timestamps can't be trusted. But a reply to a text always comes after the text. Lamport clocks keep only that "reply after text" rule.
Actually
NTP (RFC 5905): "servers and clients are precise within a few tens of milliseconds" on the Internet, and "clients on fast LANs are within a few hundred microseconds". Lamport 1978, rule IR2: "Upon receiving a message m, process Pi sets Ci greater than or equal to its present value and greater than Tm." Spanner: TT.now() returns "[earliest, latest]"; "ε is typically a sawtooth function of time, varying from about 1 to 7 ms ... ε is therefore 4 ms most of the time", backed by "GPS and atomic clocks". With commit wait, Spanner waits until TT.after(s) is true before showing a commit, so timestamps match real order. CockroachDB uses HLC and its --max-offset default is 500 ms; nodes whose clocks drift too far shut down (Spanner paper, CockroachDB docs, checked 2026-10-25).

Never decide "which write came first" by wall clock alone. Use logical counters, vector clocks for conflicts, or bounded uncertainty plus waiting.

Source: RFC 5905 · Lamport 1978 · HLC, Kulkarni et al. 2014 · Spanner, OSDI 2012 · CockroachDB transaction layer

Failure

08Two-phase commit and why it blocks

Atomic writes across machines, at a price interview favourite

Two-phase commit (2PC) makes a transaction across several databases all-or-nothing. Phase 1: the coordinator asks each participant "prepare": write everything durably, keep locks, and vote yes or no. Phase 2: if all said yes, it sends "commit"; otherwise "abort".

Two-phase commit blocked by a coordinator crash The coordinator sends prepare to participants A and B. Both vote yes. The coordinator then crashes before sending commit, so A and B are stuck in doubt, holding locks. PREPARE PREPARE YES YES Coordinatorruns the vote Database Aparticipant Database Bparticipant Crashedno commit sent A and B stuck "in doubt"voted yes, must keep locks LEGEND Ask Vote Down Blocked
After voting yes, a participant may not decide alone: the others might have aborted.
OptionHow it handles cross-service writesCost
2PC (XA, PostgreSQL PREPARE TRANSACTION)Atomic: all commit or all abortBlocks when the coordinator dies; locks held; slow
2PC with a replicated coordinatorCoordinator state kept by consensus (as Spanner does with Paxos groups)Complex; still extra round trips
SagaA chain of local transactions; on failure run undo stepsNot isolated: others can see half-done work
Outbox + eventsWrite data and an event in one local transaction; publish laterEventually consistent
In everyday words
A wedding officiant asks both people "do you?". Both say yes. Then the officiant faints before saying "you are married". Are they married? Nobody may leave until the officiant wakes up.
Actually
Gray and Lamport: "The classic Two-Phase Commit protocol blocks if the coordinator fails." PostgreSQL warns: "It is unwise to leave transactions in the prepared state for a long time", because the transaction "continues to hold whatever locks it held" (Consensus on Transaction Commit, PostgreSQL PREPARE TRANSACTION).

2PC gives atomicity and costs availability. In microservices, prefer sagas or the outbox pattern unless you truly need all-or-nothing.

Source: Gray & Lamport · PostgreSQL docs

09Idempotency and the exactly-once myth

You can't stop duplicates; you can make them harmless core

When a reply is lost, the client retries. The server may have done the work already. So over a network you can choose at-most-once (never retry, may lose) or at-least-once (retry, may duplicate). True exactly-once delivery is impossible in general.

What you can get is exactly-once effect: at-least-once delivery plus idempotent processing, so a duplicate changes nothing.

Pattern
Idempotency key
Client sends a unique key with "charge $10". Server stores key and result. A retry with the same key returns the saved result.
Pattern
Naturally idempotent writes
"Set balance to 90" is safe to repeat. "Subtract 10" is not.
Pattern
Dedup table
Consumers record processed message ids, in the same transaction as the work.
Pattern
Version check
"Update if version = 7". A replay sees version 8 and does nothing.
HTTP · idempotency key on a payment call
POST /payments
Idempotency-Key: 8f1c2a7e-...      # same key on every retry
{"amount": 1000, "currency": "INR"}
# server: INSERT key UNIQUE; if it already exists, return the stored response
In everyday words
Pressing a lift button five times calls the lift once. Design every request to be like the lift button, not like a vending machine that drops a can each time.
Actually
Kafka's design docs: "The idempotent delivery option guarantees that resending a message will not result in duplicate entries in the log", and for read-process-write between Kafka topics "Kafka leverages transactional producer capabilities added in version 0.11.0.0 to achieve exactly once semantics". That guarantee covers Kafka-to-Kafka; writing to an outside database still needs your own idempotency or storing offsets with the data (Kafka delivery semantics; the Apache page needs JavaScript, so Confluent's copy was read).

Say "effectively once": at-least-once delivery plus idempotent handlers. Anyone promising more has a narrow scope in the small print.

Source: Kafka design: delivery semantics

10Failure detection, retries, and fault types

Heartbeats, phi accrual, backoff with jitter core

A node is suspected dead when its heartbeats stop for longer than a timeout. A fixed timeout is a guess: too short gives false alarms during a GC pause, too long gives slow recovery.

The phi accrual detector learns the normal gap between heartbeats and outputs a suspicion number, phi, instead of yes/no. Higher phi means "more surely dead". You pick the threshold.

Retries must back off. If 1,000 clients all retry after exactly 1 second, they hit the recovering server together (a "thundering herd"). Exponential backoff doubles the wait each try; jitter picks a random wait up to that limit, spreading clients out.

Upper limit of the wait before each retry (base 100 ms, doubling)

Full jitter waits a random time between 0 and this limit. Computed from the formula below; a cap stops growth.

Attempt 0
100ms
Attempt 1
200ms
Attempt 2
400ms
Attempt 3
800ms
Attempt 4
1,600ms
Attempt 5
3,200ms
Show the full table
Attemptbase × 2^attempt (ms)Full jitter wait
0100random 0 to 100 ms
1200random 0 to 200 ms
2400random 0 to 400 ms
3800random 0 to 800 ms
41,600random 0 to 1,600 ms
53,200random 0 to 3,200 ms
AWS Architecture Blog · three jitter formulas
# Full jitter
sleep = random_between(0, min(cap, base * 2 ** attempt))
# Equal jitter
temp = min(cap, base * 2 ** attempt); sleep = temp/2 + random_between(0, temp/2)
# Decorrelated jitter
sleep = min(cap, random_between(base, sleep * 3))

Fault types. Everything on this page assumes crash faults: nodes stop or go slow, but never lie. Byzantine faults are nodes that send wrong or malicious messages; tolerating f of them needs at least 3f + 1 nodes, which is why blockchains, not databases, pay that cost.

In everyday words
When a shop reopens after a power cut, if everyone rushes back at 10:00 sharp, the door jams. If each person waits a random few minutes, the queue flows.
Actually
Akka: "phi = -log10(1 - F(timeSinceLastHeartbeat))" and "The default threshold is 8"; Cassandra's cassandra.yaml shows # phi_convict_threshold: 8 as the default. Marc Brooker (AWS, 2015): "Of the jittered approaches, 'Equal Jitter' is the loser", and between Decorrelated and Full Jitter "is less clear. The 'Full Jitter' approach uses less work, but slightly more time." Byzantine Generals (1982): "no solution with fewer than 3m + 1 generals can cope with m traitors" (AWS backoff and jitter, Akka failure detector, Lamport et al. 1982).

Timeouts guess, phi accrual guesses better. Always cap retries, back off exponentially and add jitter, and only retry idempotent calls.

Source: Exponential Backoff And Jitter · Builders' Library: timeouts, retries, backoff · Akka · cassandra.yaml

Practice

—When to use what

Situation, pick, why decision table

SituationPickWhy
Need one leader for a job (scheduler, cron)Lease in etcd or ZooKeeperConsensus already done for you
Lock protects a shared resourceLock + fencing token checked by the resourceSurvives pauses and expired leases
Money balances, inventory countsLinearizable store (CP)Old reads cause double spending
Likes, views, social feedEventual consistency (AP)Speed and uptime matter more than exact numbers
Detect concurrent editsVector clocks or version numbersWall clocks cannot tell "concurrent"
Global ordering with real timeTrueTime-style bounded clocks or HLCCommit wait or max-offset makes order safe
Write to two services togetherSaga or outbox; 2PC only inside one database product2PC blocks on coordinator failure
Calling a flaky dependencyTimeout + capped exponential backoff + full jitter + idempotency keyAvoids thundering herd and double effects
Cluster size for consensus3 (survives 1) or 5 (survives 2)Even sizes add cost, not safety

—Common mistakes

Things that sound right and are wrong traps

Trap
"We chose CA"
Not an option with more than one machine. Partitions happen; you choose what to do then.
Trap
Leader election with heartbeats only
A slow network makes two nodes each think the other died. Use a majority vote.
Trap
Lock without fencing
A paused holder wakes up and writes after its lease expired.
Trap
Ordering by timestamps
NTP error is tens of ms. Close writes can be ordered wrongly.
Trap
"Kafka gives exactly once"
Only Kafka-to-Kafka with transactions. Side effects elsewhere still need idempotency.
Trap
Retrying without jitter
Synchronised retries knock a recovering service down again.
Trap
Retrying non-idempotent calls
A lost reply plus a retry charges the card twice.
Trap
Four-node consensus cluster
Needs 3 votes, survives only 1 failure, same as 3 nodes.

—Interview questions

20 questions, easy to hard, with model answers practice

State the CAP theorem correctly. easy
Show answer
If the network partitions, a distributed system must choose between consistency (every read sees the latest write, in the linearizable sense) and availability (every non-failing node answers). It is not "pick 2 of 3": partitions are not optional, so the real choice is C or A during a partition. Brewer himself called the "2 of 3" framing misleading in 2012. Example: etcd refuses writes on the minority side (CP); Cassandra at ONE answers with possibly old data (AP). Follow-up: "What about when there's no partition?" That's PACELC.
What is PACELC and why does it matter more day to day? medium
Show answer
PACELC (Abadi, 2012): if Partition, choose Availability or Consistency; Else, choose Latency or Consistency. Partitions are rare, but the latency cost of waiting for replicas is paid on every request. Dynamo, Cassandra and Riak default to PA/EL: fast and available, weaker consistency. VoltDB, Megastore and HBase are PC/EC. In design interviews this is how I justify, say, reading from a local replica: I accept slightly old data to save a cross-region round trip of tens of milliseconds.
Name the fallacies of distributed computing. Which bites most? easy
Show answer
The network is reliable; latency is zero; bandwidth is infinite; the network is secure; topology doesn't change; there is one administrator; transport cost is zero; the network is homogeneous. The first bites most: a missing reply can mean the request was lost, the server crashed, it is slow, or the reply was lost. So every remote call needs a timeout, a retry policy, and an idempotent design so retries are safe.
Linearizable vs eventual consistency: explain with an example. easy
Show answer
Linearizable: the system behaves like one copy; once a write finishes, every later read by anyone returns it. Eventual: replicas may return old values for a while but converge if writes stop. Example: after I change my password, a linearizable store guarantees the old one stops working everywhere at once; an eventual store might accept it for a few hundred milliseconds on a lagging replica. Linearizable costs latency and fails on the minority side of a partition; eventual stays up and fast.
What is causal consistency and when is it enough? medium
Show answer
If operation B depends on A (B read A's result, or the same client did A then B), every node shows A before B. Unrelated writes may appear in any order. It's enough for comments and chats: a reply never appears before its question. It is the strongest model that stays available during partitions if clients stick to one server ("sticky available" in Jepsen's terms). Implemented with dependency tracking, like vector clocks or session tokens; MongoDB offers causally consistent sessions.
Why do we need consensus? Why not just heartbeats? medium
Show answer
To make several machines agree on one value, such as who is leader or what the next log entry is, even if some crash. Heartbeats only tell you that you haven't heard from someone; on a slow or split network, two nodes can each conclude the other is dead and both act as leader. Consensus adds majority voting and numbered terms, so at most one leader per term can exist. Google's SRE book: leader election "cannot be solved correctly by using heartbeats".
Walk me through a Raft leader election. medium
Show answer
A follower hears no heartbeat for its randomized election timeout (the paper's example is 150 to 300 ms). It becomes a candidate, increments the term, votes for itself and sends RequestVote. Each server votes once per term, first come first served, and only if the candidate's log is at least as up-to-date as its own. With a majority, it becomes leader and sends heartbeats. If the vote splits, the term times out and a new election starts; random timeouts make repeats rare. A leader seeing a higher term steps down.
When is a Raft log entry committed? What if the leader crashes right after? hard
Show answer
When the leader that created it has stored it on a majority of servers. If the leader then crashes, any new leader must win votes from a majority, and the up-to-date check means it overlaps with at least one server that has the entry, so it has the entry too (Leader Completeness). Entries that were only on a minority may be overwritten. A subtle rule: a leader only counts replicas for entries from its own current term; older-term entries become committed indirectly.
Why are consensus clusters 3 or 5 nodes, not 4 or 6? easy
Show answer
A majority of n is floor(n/2) + 1. Three nodes need 2 and survive 1 failure; four need 3 and still survive only 1. So the fourth node adds cost and an extra machine that can fail, with no extra fault tolerance. Five survive 2, seven survive 3. More nodes also slow writes since more acks are needed. The etcd FAQ says the same: going to an even size "doesn't buy additional fault tolerance".
Explain Paxos in 30 seconds. hard
Show answer
Roles: proposers, acceptors, learners. Phase 1: a proposer picks a unique number n and sends prepare(n); each acceptor promises to ignore anything numbered below n and returns any value it already accepted. Phase 2: with promises from a majority, the proposer sends accept(n, v), where v is the highest-numbered already-accepted value, or its own if none. When a majority accepts, v is chosen. Multi-Paxos keeps a stable leader to skip phase 1 per entry, which ends up close to Raft.
How would you implement leader election for a cron service with 3 instances? medium
Show answer
Don't write consensus; use etcd or ZooKeeper. With etcd: each instance creates a lease (say 10 s TTL) and campaigns using the concurrency election API; the winner keeps the lease alive and runs jobs. With ZooKeeper: each creates an ephemeral sequential znode; lowest number leads, others watch the next-lower node. The leader must stop work if it loses its lease, and jobs must be idempotent or fenced, because a paused old leader may still run a job once.
What is a fencing token and why is a lock with a TTL not enough? hard
Show answer
A lock with a TTL fails when the holder pauses: GC, swapping or a slow network. The lease expires, another client gets the lock, then the first wakes and writes, and both write. A fencing token is a number that grows every time the lock is granted (33, then 34). Clients send it with every write and the storage rejects any token lower than the highest it has seen. Kleppmann's 2016 post shows this exact GC-pause case. etcd's revision numbers can serve as tokens.
Why can't you order events by timestamp across servers? medium
Show answer
Clocks drift and NTP only bounds the error: RFC 5905 says "a few tens of milliseconds" over the Internet, a few hundred microseconds on a LAN. Clocks can also jump backwards after a sync. So two writes 5 ms apart on different machines can be ordered wrongly, and last-write-wins then silently drops the newer one. Fixes: Lamport clocks for causal order, vector clocks to detect concurrency, HLC to stay close to real time, or TrueTime with commit wait.
Lamport clock vs vector clock? medium
Show answer
A Lamport clock is one counter: increment per event, send it, and on receive take max plus 1. It ensures if A caused B then L(A) < L(B), giving a total order with node-id tie-breaks, but a smaller number doesn't prove causation. A vector clock keeps one counter per node; comparing two vectors tells you A before B, B before A, or concurrent. That's why Dynamo uses vector clocks: concurrent versions are real conflicts it returns to the app. Cost: size grows with the number of nodes.
How does Spanner get external consistency with TrueTime? hard
Show answer
TrueTime returns an interval [earliest, latest] with bounded uncertainty ε, kept small with GPS and atomic clocks; the paper reports about 1 to 7 ms, 4 ms most of the time. A transaction takes commit timestamp s = TT.now().latest, then waits until TT.after(s) is true before making the commit visible: "commit wait". After that wait, s is guaranteed to be in the past everywhere, so timestamp order matches real-time order. The cost is about 2ε of added latency per write, which is why small ε matters.
What is two-phase commit and what is its main problem? medium
Show answer
Phase 1: the coordinator asks all participants to prepare; each makes the change durable, holds locks and votes yes or no. Phase 2: commit if all said yes, else abort. Problem: it blocks. If the coordinator crashes after participants vote yes, they cannot decide alone, since another may have voted no, so they hold locks until it returns. Gray and Lamport: "The classic Two-Phase Commit protocol blocks if the coordinator fails." Mitigation: replicate the coordinator's decision with consensus, or avoid 2PC with sagas.
Is exactly-once delivery possible? medium
Show answer
Not as pure delivery over an unreliable network: if an ack is lost, the sender must either retry (possible duplicate) or not (possible loss). What we build is exactly-once effect: at-least-once delivery plus idempotent processing, via idempotency keys, dedup tables, or upserts. Kafka's idempotent producer and transactions give exactly-once for read-process-write between Kafka topics; once you call an external API or database, you still need your own dedup or to store offsets with the data atomically.
Design retries for a payment service calling a bank API. design
Show answer
(1) Generate an idempotency key per payment and send it on every attempt; the bank (or our own table) dedups. (2) Set a timeout per attempt, say 2 s, and an overall deadline. (3) Retry only on safe errors: timeouts, 503, connection reset; never on 400. (4) Capped exponential backoff with full jitter: sleep = random(0, min(cap, base·2^n)), max 3 to 5 attempts. (5) A circuit breaker stops retries when the bank is clearly down. (6) After a timeout, query payment status before retrying, since the charge may have succeeded.
How do you detect failed nodes? What is phi accrual? hard
Show answer
Nodes exchange heartbeats; missing them beyond a timeout marks a node suspect. Fixed timeouts are brittle: GC pauses and network jitter cause false positives. The phi accrual detector (Hayashibara et al., 2004) learns the distribution of heartbeat gaps and computes phi = -log10(probability the heartbeat is still coming). Phi grows as silence lengthens relative to normal. Akka and Cassandra default to a threshold of 8. Higher threshold: fewer false alarms, slower detection. Always pair detection with fencing, since a suspect may be alive.
Design a distributed rate limiter that stays correct during a network partition. design
Show answer
First pick a CAP side. Strict limits (paid API quotas): CP, a central counter in Redis or a consensus store; during a partition, the cut-off side fails closed or open by policy. Most rate limits are soft: AP is fine, so each region or node keeps local counters with a share of the quota and syncs periodically, accepting slight overshoot. Use token buckets, keys per user, TTLs to expire state, and idempotent increments. I'd state the overshoot bound explicitly, e.g., "at most N regions × local share".

—Cheat sheet

Numbers and facts to remember memorise

FactValueSource
CAP choiceOnly during a partition: C or AGilbert & Lynch 2002, Brewer 2012
PACELC: Dynamo, Cassandra, RiakPA/ELAbadi 2012
Majority of nfloor(n/2) + 1Raft paper
Failures survived: 3 / 5 / 7 nodes1 / 2 / 3etcd FAQ
Raft example election timeout150–300 ms, randomRaft paper
NTP accuracytens of ms (Internet), hundreds of µs (LAN)RFC 5905
Spanner ε1–7 ms, about 4 ms typicalSpanner paper
CockroachDB --max-offset500 ms defaultCockroachDB docs, checked 2026-10-25
Phi accrual threshold8 defaultAkka docs, cassandra.yaml
Full jitterrandom(0, min(cap, base*2**n))AWS Architecture Blog
Byzantine tolerance3f + 1 nodes for f faultyLamport et al. 1982
2PC weaknessBlocks if coordinator failsGray & Lamport

—Sources

All checked 2026-10-25 references

WhatWhere
Fallacies of distributed computing (secondary; no Sun original)Wikipedia, Rotem-Gal-Oz
CAP proof (Gilbert & Lynch 2002)BrewersConjecture-SigAct.pdf
CAP Twelve Years Later (Brewer 2012)infoq.com
PACELC (Abadi 2012)abadi-pacelc.pdf
Consistency modelsjepsen.io/consistency/models
Raft paper and siteraft.pdf, raft.github.io
Paxos Made Simple (Lamport 2001)paxos-simple.pdf
Google SRE book: Managing Critical Statesre.google
ZooKeeper recipeszookeeper.apache.org
etcd FAQ and concurrency packageetcd.io, pkg.go.dev
How to do distributed locking (Kleppmann 2016)martin.kleppmann.com
Time, Clocks, and the Ordering of Events (Lamport 1978)time-clocks.pdf
Vector clocks (Mattern 1988)VirtTime_GlobState.pdf
Hybrid logical clocks (Kulkarni et al. 2014)2014-04.pdf
CockroachDB start flags, transaction layercockroach-start, transaction-layer
Spanner (Corbett et al., OSDI 2012)osdi12-final-16.pdf
NTP v4 (RFC 5905)rfc-editor.org
Consensus on Transaction Commit (Gray & Lamport)twophase-revised.pdf
PostgreSQL PREPARE TRANSACTIONpostgresql.org
Kafka delivery semantics (Confluent copy of Apache doc)docs.confluent.io
Phi accrual: Akka docs, cassandra.yamldoc.akka.io, cassandra.yaml
Exponential Backoff And Jitter (Brooker 2015)aws.amazon.com
Builders' Library: timeouts, retries, backoffbuilder.aws.com
Byzantine Generals (Lamport, Shostak, Pease 1982)byz.pdf
Dynamo vector clocksDynamo paper §4.4