10. Replication & Consistency Models
10.1 Three replication topologies
| Topology | Writes go to | Conflicts? | Used by |
|---|---|---|---|
| Single-leader | One leader per partition | No (leader orders writes) | Postgres, MySQL, Kafka partitions, most systems |
| Multi-leader | Any of several leaders (often one per region) | Yes, so they need resolution | Multi-region active-active setups, offline-first apps |
| Leaderless | Any N replicas, with quorums | Yes (resolved on read / anti-entropy) | Dynamo, Cassandra, Riak |
10.2 Quorums
With N replicas, a write waits for W acknowledgements and a read queries R replicas. If R + W > N, every read quorum overlaps every write quorum, so it sees at least one copy of the latest write.
N=3, W=2, R=2 → overlap guaranteed; tolerates 1 node down for both reads and writes
N=3, W=1, R=1 → fastest, may read stale data
N=3, W=3, R=1 → fast reads, but writes fail if any node is down
The caveats show depth: sloppy quorums with hinted handoff can break the overlap guarantee; concurrent writes still need conflict resolution; and "R+W>N" alone does not give linearizability without extra mechanisms (read repair before returning, etc.).
10.3 CAP and PACELC, stated correctly
CAP: during a network Partition, a system must choose between Consistency (refuse some requests) and Availability (answer with possibly stale or conflicting data). It is not "pick two of three": partitions aren't optional.
PACELC is more useful in practice: if Partitioned, choose A or C; Else (normal operation), choose Latency or Consistency.
| System | P → | E → |
|---|---|---|
| Dynamo / Cassandra (default) | A | L |
| Spanner | C | C (pays latency for consensus + commit wait) |
| MongoDB (majority writes) | C | C (configurable) |
| Postgres single primary + async replicas | C (on primary) | L for replica reads |
10.4 The consistency spectrum
Strongest ──────────────────────────────────────────────────────────▶ Weakest
Linearizable → Sequential → Causal → Read-your-writes / Monotonic reads → Eventual
(single-copy (global order (cause before (session guarantees) (converges
illusion, but not real- effect) eventually)
real-time) time)
Pick per operation, not per system. In a social app:
- Username uniqueness, payments, inventory decrement: linearizable (or a serializable transaction).
- Seeing your own new post: read-your-writes.
- Comment replies appearing after the comment they reply to: causal.
- Like counts, view counts, follower counts: eventual.
10.5 Conflict resolution
| Technique | How | Caveat |
|---|---|---|
| Last-writer-wins (LWW) | Highest timestamp wins | Silently drops writes; clock skew picks the "wrong" winner |
| Version vectors / vector clocks | Detect concurrent writes; keep siblings for the app to merge | Complexity pushed to the application |
| CRDTs | Data types that merge deterministically (G-Counter, OR-Set, RGA for text) | Not every invariant is expressible; metadata overhead |
| Application merge | Domain logic (merge shopping carts, union of items) | Must be designed for each type |
| Avoid conflicts | Route each key's writes to a home region (single-writer per key) | Remote users pay latency for writes |
10.6 🏗️ Flow: quorum write, stale read, and read repair (N=3, W=2, R=2)
Write v2 (W=2) Read (R=2)
────────────── ──────────
Coordinator ──v2──▶ A ✓ Coordinator ──▶ A: v2 (ts 105)
──v2──▶ B ✓ → ack client ──▶ C: v1 (ts 100) (C missed the write)
──v2──▶ C ✗ (down → hint stored on D) → return v2 (newest)
→ READ REPAIR: send v2 to C
Later: C returns → D replays hint (hinted handoff)
Background: Merkle-tree anti-entropy finds remaining differences
Multi-leader conflict timeline:
US region: title="Q3 plan" at t=100 ──┐
EU region: title="Q3 Plan v2" at t=101 ─┼─ async replication crosses ─▶ both regions see 2 versions
LWW → keeps "Q3 Plan v2" (higher ts) — the US edit silently vanishes.
If EU's clock is 5 s behind, the *older* edit may win. Clock skew = data loss under LWW.
10.7 🌍 In production
- Amazon Dynamo (2007): vector clocks and client-side merge for the cart. The paper openly says deleted items could reappear. That was a deliberate business trade-off.
- Azure Cosmos DB exposes five consistency levels: Strong, Bounded Staleness, Session (the default: read-your-writes within a session token), Consistent Prefix, and Eventual. It's a good real-world example that consistency is a per-workload product choice, priced and documented.
- Google Spanner: external consistency through TrueTime (§19.4). It's the "pay latency for strong consistency" end of PACELC.
- Facebook TAO (2013): a graph cache over MySQL with read-after-write consistency within a region (writes go through the cache tier) and eventual consistency across regions, which is enough for a social graph.
- Jepsen (Kyle Kingsbury) has tested many databases and found consistency claims that didn't hold under partitions. Use it as an interview reference: "I'd check the Jepsen analysis before trusting the documented guarantee."
10.8 ⚠️ Corner cases & deep details
- R + W > N is not linearizable by itself. Concurrent read and write can return new-then-old values to two readers. You need read repair before returning, or a consensus-based design.
- Sloppy quorums (writes accepted by substitute nodes during failures) keep you available, but the quorum overlap guarantee is gone until hints are delivered.
- Zombie data: a delete is a tombstone. If a replica is down longer than the tombstone grace period and repair never runs, the deleted data comes back when that replica returns.
- Session guarantees across devices: read-your-writes on a phone doesn't carry over to the laptop unless the session token or position is shared via the user account.
- Causality across services: a comment is stored in service A and the post it replies to in service B. A reader can see the comment before the post. Fix it by carrying causal metadata (a version or position) and making readers wait for, or filter on, missing dependencies.
- Uniqueness checks against replicas: "is this username taken?" checked on a lagging replica returns "free" for a name that was just taken. Uniqueness checks must hit the linearizable source (primary or uniqueness table).
- CRDT metadata growth: OR-Sets keep tombstones or unique tags, and text CRDTs keep per-character IDs. Without garbage collection, which itself needs coordination, documents grow without bound.