Part 4 · Redundancy
Replication II: Multi-Leader, Leaderless, Quorums, and Conflicts
Lesson 08 gave us one leader: every write went through a single node, followers caught up behind it, and the cost was replication lag plus a failover scramble when that one writer died. But one writer is also a bottleneck and a single point of failure — a user in Singapore still pays a round-trip to a leader in Virginia, and an offline app cannot write at all. This lesson lifts the "one writer" assumption. Once several nodes can accept writes, you buy local low-latency writes and survive a node loss without a failover — but you inherit a new, harder problem: two writes to the same record can happen with no agreed order between them, and the system must decide what "the value" even is. This is the home of the quorum condition w + r > n.
New capability: Reason about systems where more than one node accepts writes; size a leaderless quorum so reads see the latest write; and choose a conflict-resolution strategy that does not silently drop data.
1 · Multi-leader: local writes, global conflicts
What this is: let several nodes accept writes — and pay for it in conflicts.
In single-leader replication (lesson 08) exactly one node accepts writes; everyone else is a read-only follower. Multi-leader replication (also called master-master or active-active) relaxes that: more than one node accepts writes, and each leader is also a follower of the others, streaming its writes to them. Two situations make this worth the trouble:
(A third case is real-time collaborative editing — a shared doc or whiteboard, where each user's client applies edits locally and syncs to the others, exactly the offline-client pattern at sub-second scale.) The thing you buy is availability and local latency. The bill arrives as the write conflict: the same logical record edited in two leaders before either has heard from the other. User A in the EU sets their display name to "Sam"; at the same wall-clock moment User A on their phone (in the US region) sets it to "Samantha". Each leader accepts its write locally and is correct locally. When the two writes replicate and meet, there is no single "happened-before" order between them — they are concurrent — and the system must decide what the name now is.
2 · Leaderless: write to many, read from many
What this is: no leader at all — the client writes to many replicas and reads from many.
Leaderless replication (the Dynamo-style design behind Cassandra, Riak, and Amazon's DynamoDB lineage) drops the idea of a leader entirely. There are n replicas of each piece of data. To write, the client (or a coordinator acting for it) sends the write to all n replicas and waits for w of them to acknowledge. To read, the client queries several replicas and waits for r of them to respond, then uses the freshest value among the responses. There is no failover step (lesson 08) because there is no leader to fail — a down replica is just one fewer ack, and writes keep flowing.
Two background mechanisms stop a leaderless store from drifting apart forever:
- Read repair. When a read collects responses from several replicas and notices one is stale (an older version than another), it writes the newer value back to the stale replica. Reads thus opportunistically heal the data they touch — great for frequently-read keys, useless for keys no one reads.
- Anti-entropy. A background process that continuously compares replicas and copies over missing or stale values, covering the rarely-read keys read repair never touches. The naive way to compare two replicas holding millions of keys is to ship every key and value across the network and diff them — far too expensive to run continuously. The standard fix is a Merkle tree, and its mechanism is worth seeing.
A Merkle tree (hash tree) summarizes a replica's whole keyspace as one number, then lets two replicas zoom in on exactly where they disagree without shipping the data. Build it bottom-up: split the key range into many small buckets, hash the contents of each bucket into a leaf, then hash each pair of children into a parent, repeating up to a single root hash that fingerprints the entire range.
The payoff is the descent: if the two replicas are identical, a single root-hash comparison proves it — no data moves at all. If they differ in one bucket, you follow only the path whose hashes mismatch and ignore every subtree whose hashes match, so you localize the divergence in about log(N) hash comparisons instead of an N-key scan, and you ship only the handful of keys in the differing leaf. That is what makes anti-entropy cheap enough to run forever in the background; Dynamo and Cassandra use exactly this, and equal root hashes across replicas are the "evidence it is correct" in this lesson's Where is truth? artifact below.
Why does this give you anything safe? Because of how w and r are sized relative to n — the quorum condition, next.
3 · The quorum condition: w + r > n
What this is: size the read and write sets so they are forced to share a node.
Here is the whole idea in one line: if the write set and the read set must overlap, then a read is forced to touch a node that took the latest write. A quorum is "enough nodes that any two such groups must share at least one member." Among the replicas a read contacts, that shared node holds the newest value — and since the read uses the freshest response it gets, it sees the latest write.
When must a set of w nodes and a set of r nodes (both drawn from n replicas) be forced to overlap? Think of the worst case: the write hit one group of w nodes, the read hit a different group of r nodes, and they avoided each other as much as possible. They can stay disjoint only while w + r fits inside n without touching. The moment
w + r > n
there are too many nodes between the two sets to keep them apart — by the pigeonhole principle they must share at least one node. That shared node took the write and is in the read, so the read sees it. That is the entire guarantee.
n = 5, w = 3, r = 3. Then w + r = 6 > 5 ✓, overlap guaranteed. Now you tolerate 5 − 3 = 2 down replicas on each of the read and write paths — more durable and more fault-tolerant, at the cost of waiting for 3 acks/responses instead of 2 (higher tail latency, since you wait for the 3rd-slowest node).
The trade is a dial. Want fast writes, accept staleness? Set w = 1, r = 1 with n = 3: w + r = 2 ≤ 3 — no overlap guarantee. Want read-heavy with strong reads? Set w = n, r = 1: every write goes everywhere, any single replica is current. The condition w + r > n is the line between "a read is guaranteed to see the latest committed write" and "maybe not."
The fine print (sloppy quorums and hinted handoff). Even with w + r > n, a quorum is weaker than a leader. Two caveats matter most. First, a sloppy quorum: when some of the n "home" replicas are unreachable, the system can accept the w acks from other reachable nodes outside the home set rather than failing the write. This raises write availability, but the write landed on nodes that are not in the read's home set, so the overlap guarantee no longer holds until the data is moved back — temporarily, even w + r > n can return stale reads. The repair step is hinted handoff: the stand-in node holds the write with a "hint" of where it really belongs and forwards it to the proper home replica once that node comes back. Sloppy quorum + hinted handoff is a tunable relaxation: it trades the overlap guarantee for staying writable during partial outages.
4 · Feel the knobs: the quorum widget
Set n, w, and r below. The widget draws the n replicas as a ring, shows one worst-case write set (blue) and read set (amber) pushed as far apart as possible, marks any node that is in both (the overlap, green), and tells you whether w + r > n holds and how many node failures each path tolerates.
5 · Conflict resolution: detect first, then resolve without losing data
What this is: tell "one write followed the other" from "they were concurrent" — then merge without loss.
Whether conflicts came from two leaders (§1) or two concurrent leaderless writes (§2), you face the same question: two values exist for one key. The key distinction is order. If write B saw A's effect and built on it, B happens-after A and simply supersedes it — no conflict. If neither saw the other, they are concurrent, and that is a true conflict you must resolve. There are three families of answer, in increasing safety.
Last-write-wins (LWW), and why it silently drops data
Last-write-wins attaches a timestamp to every write and, on conflict, simply keeps the one with the larger timestamp and discards the rest. It is appealingly simple and it always converges — every replica ends up agreeing. The catastrophe is the word "discards": LWW silently drops the losing write. If "Sam" and "Samantha" were genuinely concurrent edits, LWW throws one away with no record that it ever existed — and worse, it decides the winner by clock order, which is not causal order. Clocks on different machines drift (lesson 15 makes this precise); a write that physically happened later can carry an earlier timestamp and lose. LWW is acceptable only when losing concurrent writes is genuinely fine (e.g., a cache entry, an idempotent "last seen" timestamp).
Version vectors: detect that two writes were concurrent
To do better than guessing, the system must first know two writes were concurrent rather than one following the other. A version vector (a generalization of a vector clock to per-replica counters) does this: each replica keeps a counter, and a value carries the vector of counters it has "seen." Given two versions, you can compare their vectors — if one vector is element-wise ≥ the other, that write causally happened-after and supersedes it; if neither dominates, the writes are concurrent and you have a true conflict. The point is detection, not resolution: the version vector turns "two values exist" into "these two values are causally concurrent siblings" so the system can stop pretending one is simply newer.
Siblings, CRDTs, and application merge: resolve without loss
Once concurrency is detected, you have honest options that don't drop data:
- Keep siblings. Store both concurrent values and return them together on the next read (Dynamo/Riak do this). The application — or the user — merges them. The shopping-cart classic: if one device added "milk" and another added "eggs" concurrently, return both carts and union the items, so nothing is lost.
- CRDTs (Conflict-free Replicated Data Types). Data types whose merge rule is mathematically guaranteed to converge regardless of order — e.g., a grow-only set, an add/remove set, or a counter. The structure is the conflict resolution: any two replicas that have seen the same writes compute the same merged value automatically. This is what powers offline-capable collaborative apps.
- Application-level merge. Domain-specific logic: union the cart, take the max of a high-water mark, run operational transforms on a text document. Right when the domain has a meaningful merge; the cost is you must write and test that merge function.
The recurring lesson: conflict resolution belongs in the data model. A shopping cart merges by union; a collaborative doc uses CRDTs; a bank balance cannot merge by averaging and so should not be multi-writer at all. Choosing multi-leader or low-quorum leaderless is choosing to make these decisions explicit instead of letting a clock make them for you.
Trade-offs
| Choice | Buys | Costs |
|---|---|---|
| Multi-leader | Local low-latency writes, region/datacenter independence, offline edits | Write conflicts you must detect and resolve |
| Leaderless quorum | No leader to fail over, tunable read/write/durability via n, w, r | Read repair + anti-entropy machinery; richer anomaly surface |
| w + r > n | Read guaranteed to see the latest committed write | Higher tail latency (wait for more acks/responses) |
| Sloppy quorum + hinted handoff | Stays writable during partial outages | Temporarily breaks the overlap guarantee until handoff completes |
| Last-write-wins | Trivial, always converges | Silently drops concurrent writes; trusts unreliable clocks |
| Version vectors + siblings / CRDT / merge | Detects concurrency; resolves without data loss | More storage and a domain-specific merge to write |
Failure modes
- LWW data loss. Concurrent writes silently collapse to one, chosen by a possibly-skewed clock. Symptom: edits "disappear" with no error.
- Quorum sized too low. w + r ≤ n ships stale reads that look like a caching bug. Symptom: a value you just wrote isn't there on the next read from another replica.
- Sloppy-quorum staleness. During a partition, writes land outside the home set; reads briefly miss them even though w + r > n. Symptom: transient stale reads correlated with node outages.
- Repair falls behind. Rarely-read keys never get read-repaired and anti-entropy is throttled; replicas diverge for a long time. Symptom: cross-region feature values disagree though every node is "healthy."
- Conflict shoved onto data that can't merge. Multi-writer on a balance or a uniqueness constraint — there is no safe automatic merge.
Decision checklist
- Does this data tolerate conflicts (cart, notes) or forbid them (balance, unique key)? If it forbids them, keep it single-leader (lesson 08).
- If leaderless: pick n, then choose w and r for your read/write mix; verify w + r > n if you need fresh reads.
- How many simultaneous replica failures must each path survive? That sets n − w and n − r.
- Are you using a sloppy quorum? If so, document that fresh-read guarantees lapse during outages.
- What detects concurrency — timestamps (LWW, lossy) or version vectors (honest)?
- What merges siblings — a CRDT, an app-level merge, or the user? Is that merge tested?
- Is read repair on, and is anti-entropy keeping up under load?
Checkpoint exercise
Where this points next
Replication answered "how do we keep copies of the same data consistent?" — whether behind one leader (08) or many writers (this lesson). Multi-leader had a striking limit case: a phone or laptop that accepts writes while disconnected and syncs later is just a leader with a flaky, often-broken link to the others. Push that to the extreme and you get local-first software, where the device's own copy is the primary and the network is a best-effort sync channel rather than a precondition for writing. The next lesson, Local-First and Offline Sync (lesson 10), takes the conflict machinery you just built — version vectors, CRDTs, sibling merge — and runs it at the edge: every device is its own leader, sync is intermittent, and convergence-without-data-loss stops being an option and becomes the whole design. It is the most aggressive form of the redundancy this part is about, and the natural close before we leave copies behind and start splitting the data itself.
Interview prompts
- When would you choose multi-leader over single-leader replication, and what new problem does it create? (§1 — multi-datacenter and offline clients gain local low-latency writes and independence; the cost is write conflicts, since two leaders can accept concurrent edits to one record with no agreed order.)
- State and justify the quorum condition. (§3 — w + r > n forces any write set of w and read set of r drawn from n replicas to share at least one node (pigeonhole), so the read touches a replica holding the latest write and sees it.)
- With n = 5, you want to survive two replica failures on reads and still read fresh. Give w and r. (§3 — r = 3 tolerates 5 − 3 = 2 down on reads; pick w = 3 so w + r = 6 > 5. w = 3 also survives 2 down on writes.)
- What goes wrong with n = 3, w = 1, r = 1? (§3 — w + r = 2 ≤ 3; the write may ack on one replica and the read may hit a different one, returning a stale value. Fast, but no fresh-read guarantee.)
- Why is last-write-wins dangerous, and what should replace it? (§5 — it silently discards concurrent writes and decides by clock order, which isn't causal order and is subject to clock skew; detect concurrency with version vectors, then keep siblings / use a CRDT / merge in the app.)
- What are read repair and anti-entropy, and why do you need both? (§2 — read repair heals stale replicas on the keys reads happen to touch; anti-entropy is a background compare-and-sync (often Merkle trees) that reaches the rarely-read keys read repair never sees.)
- What does a sloppy quorum with hinted handoff buy and cost? (§3 — it accepts w acks from nodes outside the home set so writes stay available during a partition; the cost is that the overlap guarantee lapses until the stand-in hands the write off to its true home replica.)