Tier 1 · Data

Replication

One leader, many leaders, or none — and what each one costs you

⏱ 18 min dataavailabilityconsistency 📕 Ch 6 — Design a Key-Value Store

Why replicate at all

Three reasons, and it matters that you can name them separately, because a design that serves one may actively harm another:

  1. Availability — a machine dies and the data is still there.
  2. Read throughput — spread reads across copies.
  3. Locality — put a copy near the user so reads don't cross an ocean.

The tension: availability wants copies to be independent, consistency wants them identical, and you can only keep them identical by making writes wait. Every replication scheme below is a different answer to how long the write waits, and for whom.

Single-leader replication

One node accepts writes. Everyone else follows.

The leader appends each write to a log and ships it to followers. What's in that log is a real design decision:

Log type What ships Trade
Statement The literal UPDATE … Tiny, but NOW(), RAND(), and triggers diverge. Mostly abandoned.
Write-ahead log (WAL) Physical byte-level page changes Exact, but couples replicas to the storage engine version — blocks rolling upgrades.
Logical / row-based "Row X changed from A to B" Decoupled from storage internals, parseable by outside consumers. This is what CDC pipelines tap.

Synchronous, asynchronous, and the useful middle

The knob that matters is when the leader tells the client "done".

Failover is the hard part

Leader dies. Now what?

  1. Detect it — usually a timeout. Too short: you fail over on a GC pause. Too long: you're down for that whole window. There is no correct value, only a chosen tradeoff.
  2. Elect a new leader — needs consensus, or you get two.
  3. Redirect clients, and make the old leader stand down when it returns.

Two failure modes worth naming:

Replication lag, and the three guarantees you actually want

With async replication, followers trail the leader. Usually milliseconds. Under load, seconds. During a backlog, minutes. Three distinct anomalies follow — and the fix for each is different, which is why they have separate names:

Read-your-own-writes. You write, then read from a lagging follower, and your own change is missing. That's the hook at the top. Fixes: route reads that could have been affected by your own recent writes to the leader; or track the client's last-write timestamp/LSN and only serve from a replica caught up past it.

Monotonic reads. You read from replica A (fresh), refresh, hit replica B (stale), and time appears to run backwards. Fix: pin each user to one replica, usually by hashing the user ID — not randomly per request.

Consistent prefix reads. You see an answer before the question. Fix: ensure causally related writes go through the same partition, or track causal dependencies explicitly.

Multi-leader replication

Accept writes in more than one place. You'd do this for:

The cost is a genuinely hard problem: write conflicts. Two leaders accept conflicting writes to the same record, and there is no global order to appeal to.

Resolution How Cost
Last-write-wins Highest timestamp survives Silently discards data, and clock skew decides the winner. Popular and dangerous.
Version vectors Detect concurrency, surface both versions Correct, but the application must merge
CRDTs Data types that merge deterministically Excellent where they fit (counters, sets, text); not everything fits
Application merge Hand the siblings to business logic Most control, most work

Leaderless replication

Dynamo-style. The client (or a coordinator) writes to several nodes at once and reads from several at once. No failover, because there's nothing to fail over.

With N replicas, W write acks required, and R read responses required:

If R + W > N, the read set and write set must overlap by at least one node, so a read is guaranteed to touch a node holding the newest write.

With N=3, W=2, R=2 you tolerate one node down for both reads and writes. With W=3, R=1 you get fast reads and no write availability under any failure. Staleness is repaired in the background by read repair (fix the stale replica during a read that noticed it) and anti-entropy (a background process comparing replicas, usually via Merkle trees).

Worked numbers: what synchronous replication costs in Europe

Approximate round-trip times:

Link RTT
Same rack ~0.2 ms
Same AZ ~0.5 ms
Zurich ↔ Frankfurt ~8 ms
Zurich ↔ Dublin ~28 ms
Zurich ↔ us-east-1 ~90 ms

Take a service doing a 1 ms commit, targeting 200 ms p99 for a request that makes 4 sequential writes.

The design move that falls out: synchronous within a region, asynchronous across regions, and accept that a region loss may lose the last few seconds of writes. If that loss is unacceptable, you need consensus across regions and you pay the 90 ms — there is no third option, and saying so plainly is the mark of a senior answer.

What to take away

Check yourself

  1. A user updates their profile, sees a success toast, refreshes, and the old value is back. Which guarantee is being violated?

  2. With N=3 replicas, which quorum setting gives the fastest reads while still guaranteeing a read overlaps the latest acknowledged write?

  3. Why is last-write-wins conflict resolution considered dangerous?

  4. A service commits in 1 ms and makes 4 sequential writes with a 200 ms p99 budget. Which replication topology fits while still surviving the loss of an entire datacenter?