Tier 2 · Distributed Core

Quorums, Repair & Anti-Entropy

R + W > N, what it actually buys, and the three ways it quietly stops being true

⏱ 19 min distributedreplicationconsistency 📕 Ch 6 — Design a Key-Value Store

The overlap argument

With N replicas, W acknowledgements required to call a write successful, and R responses required to serve a read:

If R + W > N, the read set and the write set cannot be disjoint. They must share at least one node, and that node holds the latest write.

It's the pigeonhole principle. Two subsets of an N-element set with sizes summing to more than N must intersect.

Play with it — kill replicas, change R and W, and watch when reads go stale:

Loading simulator…
Click a replica to kill it. The staleness figure is exact — the probability that a random R-subset misses a random W-subset.

Two things worth doing in the simulator:

Set N=5, W=3, R=2. That's 5, which is not greater than 5 — and the staleness figure is 10%, not zero. The condition is strictly >, and the off-by-one is a genuinely common mistake. R + W = N gives you nothing.

Set W=N and R=1. Staleness drops to zero and reads become a single fast hop — but the "node losses writes survive" stat drops to zero too. Any single failure blocks all writes. That's the real tradeoff the formula hides.

Choosing R and W

Config (N=3) Overlap Good for Cost
W=1, R=1 No Maximum availability and speed Frequently stale
W=2, R=2 Yes Balanced default Both ops need 2 nodes
W=3, R=1 Yes Read-heavy, fast reads No write availability under any failure
W=1, R=3 Yes Write-heavy, fast writes No read availability under any failure

The general shape: W and R trade write availability against read availability, and their sum trades both against staleness. N=3/W=2/R=2 is the common default because it tolerates one failure on both paths.

There's also a latency consequence that the availability numbers don't show. A quorum operation's latency is the latency of the W-th fastest replica, so raising W pushes you further into the tail of the response-time distribution. This is why W=N is slow in a way that averages don't reveal — you're waiting for the slowest node every single time.

The mitigation is hedged requests: send to more replicas than you need and take the first W to answer. You pay extra load to cut tail latency, and at high percentiles the trade is usually worth it.

Three ways R + W > N stops protecting you

Back to the hook. The arithmetic is fine; the assumptions underneath it are what break.

1. Sloppy quorums. In a strict quorum, the W nodes must come from the key's designated home replicas. Under a partition, those specific nodes may be unreachable — so Dynamo-style systems accept the write on whatever nodes they can reach, storing a hinted handoff: "I'm holding this for node C; deliver it when C returns."

This keeps you writing during a partition, which is the point. But the write didn't land on the home replicas, so a subsequent read of R home replicas can miss it entirely. Sloppy quorums increase durability and availability, and void the overlap guarantee.

2. Concurrent writes. The overlap guarantee says a read will see the latest write. It does not say what "latest" means when two writes happened concurrently. Two clients writing different values to different quorums both succeed, and now some replicas have one value and some the other. You need version vectors or last-write-wins to resolve it — and as clocks covered, LWW discards data.

3. Failed writes are not rolled back. If a write reaches 1 node but needed 2, the client gets an error — and the node that took it keeps it. There's no rollback. A later read may return a value from a write that was reported as failed. This is deeply counterintuitive and absolutely real.

Keeping replicas converged

Since replicas drift, something must pull them back together. Two mechanisms, and you want both:

Read repair — during a read, the coordinator notices a replica returned an old version and writes the current one back. Free, since you already did the read, but it only ever repairs data that someone reads. Rarely-read keys drift indefinitely.

Anti-entropy — a background process comparing replicas and reconciling differences. This covers the cold data read repair never touches.

The obvious implementation of anti-entropy — send every key and hash to your peer — is unaffordable. The standard solution:

Merkle trees. Build a binary tree where leaves are hashes of key ranges and each internal node hashes its children. To compare two replicas:

  1. Exchange root hashes. If equal, the replicas are identical — one hash comparison for the whole dataset.
  2. If they differ, exchange the children's hashes and recurse only into subtrees that differ.
  3. Continue until you reach the differing leaves.

Comparing two replicas holding a billion keys with three differences takes roughly 3 × log(n) hash comparisons rather than a billion. This is why Merkle trees appear in Cassandra, DynamoDB, Riak, and (for entirely analogous reasons) in Git and blockchains.

Tunable per operation

Real quorum systems let you set consistency per query, which is the practical payoff of all this. In Cassandra terms:

Mapping to earlier lessons: this is PACELC's "else" branch made into a runtime flag. Same data, same cluster, different tradeoff per query.

What to take away

Check yourself

  1. With N=5, W=3, R=2, is a read guaranteed to see the latest acknowledged write?

  2. Why can a sloppy quorum return stale data even when R + W > N?

  3. A write requires W=2 but reaches only 1 replica, so the client receives an error. What happens to the value on that one replica?

  4. Two replicas each hold a billion keys and differ in three of them. Why are Merkle trees an efficient way to find the differences?