TL;DR

1. The problem: why keep more than one copy?

Imagine Instagram's user database lived on exactly one PostgreSQL server. Four things go wrong as the product grows:

Replication — keeping a copy of the same data on multiple machines (replicas) connected by a network — addresses all four. If the data never changed, replication would be trivial: copy once. All the difficulty, and this whole lesson, is about handling changes: how writes reach every replica, how long that takes, and what users see in the meantime.

🧠 Analogy: the teacher and the note-takers A teacher (the leader) writes on the board; students (the followers) copy it into their notebooks. Anyone who wants to know what was taught can read any student's notes — reading is spread across the class. But a slow student may be a few lines behind (replication lag), so asking two different students can give two different answers. If the teacher leaves, the class must agree on which student takes over (failover) — and if two students both start writing on the board, you get chaos (split brain). If students in two classrooms each take notes and add their own (multi-leader), the notebooks will eventually disagree and need reconciling (conflict resolution).

2. Vocabulary you'll need

Leader / primary

The replica that accepts writes (in single-leader setups). Older docs say master.

Follower / replica / standby

A replica that applies the leader's changes and (usually) serves reads. Older docs say slave.

Replication log

The ordered stream of changes the leader sends — e.g. PostgreSQL's WAL, MySQL's binlog. Position in it: LSN / binlog offset / GTID.

Replication lag

How far a follower is behind the leader, in seconds or log bytes. Usually milliseconds; under load, seconds to minutes.

RPO

Recovery Point Objective: how much recent data you may lose in a failure (e.g. "≤ 1 s of writes"). Synchronous replication → RPO 0.

RTO

Recovery Time Objective: how long until service is restored after a failure (e.g. "≤ 30 s" with automatic failover).

There are three fundamental architectures — single-leader, multi-leader and leaderless — and each can be combined with sync or async propagation. We'll go through them in that order.

3. Single-leader (leader–follower) replication

The most common design, used by PostgreSQL, MySQL, SQL Server, MongoDB replica sets, Redis, and Kafka partitions:

  1. One replica is designated the leader. All writes from clients go to it.
  2. The leader writes the change to its local storage and also appends it to the replication log.
  3. Each follower pulls (or is pushed) the log and applies the changes in the same order, so it ends up with an identical copy.
  4. Reads can go to the leader or any follower.
User Service writes Feed / Profile reads Leader PostgreSQL, US-East Replica 1US-East (sync) Replica 2Europe (async) Replica 3Asia (async) INSERT/UPDATE WAL stream SELECT (nearest replica) lag ≈ 0 lag ≈ 50–200 ms lag ≈ 100–300 ms
Single-leader replication: writes go to the leader; the leader streams its write-ahead log (dashed) to followers in the same and other regions; reads are spread across replicas, preferably the nearest one.

3.1 Synchronous vs asynchronous vs semi-synchronous

The key question: when does the leader tell the client "your write succeeded"?

 SYNCHRONOUS                                   ASYNCHRONOUS
 Client     Leader      Follower               Client     Leader      Follower
   | write    |             |                    | write    |             |
   |--------> | write local |                    |--------> | write local |
   |          |--- log ---->|                    | <-- OK --|             |   ← client is done
   |          |             | apply + fsync      |          |--- log ---->|
   |          |<---- ACK ---|                    |          |             | apply (ms…s later)
   | <-- OK --|             |                    |
 Latency = local write + 1 round trip           Latency = local write only
 Leader dies after OK? data is on follower ✔     Leader dies before shipping? write LOST ✘
SynchronousAsynchronousSemi-synchronous
Leader acks client when…All (or the designated) followers have durably received the writeThe leader alone has written itAt least one (or k) follower(s) have received it
Write latency+1 round trip to the slowest required follower (cross-region: +70–200 ms)Lowest+1 round trip to the fastest follower (usually a same-region one, ~1 ms)
Data loss on leader failure (RPO)NonePossible — whatever hadn't shipped yetNone, if the acked follower is promoted
AvailabilityPoor — one slow/dead follower blocks all writesBest — followers can fall behind or die without affecting writesGood — writes continue as long as one follower responds
ExamplesPostgreSQL synchronous_commit = remote_apply with all standbys listed; financial systemsDefault in PostgreSQL and MySQL; cross-region replicasMySQL semi-sync plugin; PostgreSQL synchronous_standby_names = 'ANY 1 (r1, r2)'

In practice, "fully synchronous to every follower" is almost never used — one sick replica would freeze the system. The common production setup is: one synchronous (or semi-sync) follower in the same region for zero-data-loss failover, plus asynchronous followers in the same and other regions for read scaling and disaster recovery. Consensus-based systems (Raft/Paxos: etcd, CockroachDB, Spanner) generalise semi-sync: a write commits once a majority has it.

3.2 Adding a new follower without downtime

  1. Take a consistent snapshot of the leader (e.g. pg_basebackup), noting its exact log position (LSN / binlog coordinates / GTID).
  2. Copy the snapshot to the new machine.
  3. The follower connects to the leader and requests all changes since that position.
  4. Once it has caught up (lag ≈ 0), add it to the read pool.

The same procedure recovers a follower that crashed: it knows the last log position it applied, so it simply asks for everything after it (if the leader still retains that much log — otherwise it needs a fresh snapshot).

4. Replication lag and the anomalies it causes

With asynchronous followers, a read from a follower may return stale data. If writes stop, followers eventually catch up — that's eventual consistency. Normally lag is a few milliseconds, but it can grow to seconds or minutes when a follower is overloaded, when a big migration or batch job floods the log, or across a congested inter-region link. Three user-visible anomalies follow, each with a named guarantee that fixes it.

4.1 Read-your-writes (read-after-write) consistency

Problem: Alice edits her bio, the write goes to the leader, the app immediately reloads her profile from a lagging follower — and shows her old bio. She thinks the save failed and edits again.

 Alice's app            Leader                     Follower (200 ms behind)
    | UPDATE bio='🌴'      |                              |
    |------------------>   | commit                       |
    | <------ OK -------   |----- WAL (in flight) ----.   |
    | GET /profile/alice                               \  |
    |------------------------------------------------> |  |  (hasn't applied it yet)
    | <--------------- bio = old value  ✘ ------------ |  |
                                                        `-> applied 200 ms later

Guarantee: a user always sees their own writes (other users may still see stale data briefly). Techniques:

4.2 Monotonic reads

Problem: Bob refreshes a post's comments twice. The first request hits a fresh follower and shows Carol's new comment; the second hits a more lagged follower and the comment disappears. Time appears to go backwards.

 Bob refresh #1 ──► Follower A (lag 10 ms)   → sees comments [c1, c2, c3]
 Bob refresh #2 ──► Follower B (lag 3 s)     → sees comments [c1, c2]        ✘ c3 vanished

Guarantee: once a user has seen data at some point in time, they never later see an older state. Techniques: sticky routing — always send a given user's reads to the same replica, e.g. replica = hash(user_id) % replicas (re-route only if it fails); or track the highest log position the user has seen and only read from replicas at least that fresh.

4.3 Consistent prefix reads

Problem: causally related writes become visible out of order. In comments: Alice asks "Where was this taken?" and Bob replies "Bali!". If the two writes are stored in different partitions that replicate with different lag, a viewer may see Bob's answer before Alice's question.

 Partition P1 (fast follower):  [12:00:05] Bob:   "Bali!"
 Partition P2 (slow follower):  [12:00:01] Alice: "Where was this taken?"   ← not replicated yet
 Viewer sees:  "Bali!"  … and only later the question it answers.   ✘

Guarantee: if a sequence of writes happens in a certain order, anyone reading them sees them in that order (a prefix of the history, never a jumbled subset). Techniques: keep causally related data in the same partition (e.g. all comments of a post partitioned by post_id, so one replication stream preserves order — exactly how a Cassandra comments_by_post table is laid out); attach causal metadata (a reply references its parent's ID, and the client hides a reply until its parent is visible); or use systems offering causal consistency.

💡 How stale is "stale"? Measure lag continuously (PostgreSQL pg_stat_replication.replay_lag, MySQL Seconds_Behind_Source) and alert on it. Load balancers for read pools should eject replicas whose lag exceeds a threshold (e.g. 5 s) rather than serve badly stale data. See Monitoring.

5. Failover: when the leader dies

Followers failing is easy (route reads elsewhere, catch up later). The leader failing is hard: someone must become the new leader, clients must start sending writes to it, and the old leader must not come back thinking it's still in charge. This process is failover. It can be manual (an on-call engineer runs a promotion script) or automatic.

5.1 The steps of automatic failover

  1. Detect the failure. There's no reliable way to know a machine is dead — only that it's not answering. Nodes exchange heartbeats; if the leader misses them for a timeout (e.g. 10–30 s), it's declared dead. Too short a timeout → needless failovers during a GC pause or load spike (which can make an overload worse); too long → longer outage (higher RTO).
  2. Choose a new leader. Usually the follower with the most up-to-date log (minimising data loss), chosen by a consensus system (etcd/ZooKeeper/Consul via a tool like Patroni or Orchestrator) or by a majority vote among replicas (Raft-style election). Prefer the synchronous follower if one exists — it has every acknowledged write.
  3. Reconfigure the system. Clients must send writes to the new leader: update service discovery / DNS / a virtual IP, or let a proxy (PgBouncer, HAProxy, ProxySQL) or the load balancer follow the leader. Other followers re-point to replicate from the new leader.
  4. Deal with the old leader. When it comes back it must step down and become a follower — and any writes it accepted that were never replicated must be discarded or reconciled.

5.2 What can go wrong

5.3 Preventing split brain: quorums, leases and fencing tokens

 Lock service           Old leader L1                 New leader L2           Storage
   | grant lease, token=33 |                              |                      |
   |---------------------->|                              |                      |
   |                       |  … long GC pause / partition …                      |
   | lease expired; grant token=34                        |                      |
   |----------------------------------------------------->|                      |
   |                       |                              | write(x, token=34)   |
   |                       |                              |--------------------->| ok (max=34)
   |                       | wakes up, still thinks it leads                     |
   |                       | write(y, token=33)                                  |
   |                       |------------------------------------------------->| REJECTED: 33 < 34 ✔

Raft and Paxos have this built in: every message carries the term, and nodes reject messages from stale terms. That's why consensus-based databases (CockroachDB, Spanner, etcd) and tools like Patroni (which stores the leader key in etcd with a TTL) are trusted for automatic failover.

6. Multi-leader replication

In multi-leader (master–master, active–active) replication, more than one node accepts writes; each leader forwards its changes to all the others. Within one data center this rarely pays off, but it's compelling in three situations:

Leader USaccepts writes Leader EUaccepts writes Leader Asiaaccepts writes 📱 US users📱 EU users📱 Asia users async replication between leaders (all-to-all) ~80 ms ~150 ms local write ~2 ms local write ~2 ms local write ~2 ms ⚠️ Conflict: at 12:00:00.100 Alice sets bio = "beach" via US, and at 12:00:00.120 her tablet sets bio = "mountains" via EU. Which wins?
Multi-leader replication across regions: every region accepts writes locally (fast, partition-tolerant) and replicates asynchronously to the others — so concurrent writes to the same data in different regions can conflict.

6.1 Topologies

Leaders can replicate all-to-all (most robust; but messages can arrive in different orders at different nodes), in a circle or star (fewer links, but one failed node can break the propagation path). Each change is tagged with the IDs of nodes it has passed through to avoid infinite loops.

6.2 Conflicts and how to handle them

The big problem: two leaders can accept conflicting writes to the same record concurrently, and neither knows until replication brings them together — by then both users were told "saved". Options, from best to most complex:

  1. Conflict avoidance (most common). Make all writes for a given record go through the same leader. E.g. each user has a home region; all writes to Alice's profile go to Alice's home leader, even if she's travelling. Conflicts become impossible unless the home region changes (failover).
  2. Last-write-wins (LWW). Attach a timestamp to each write; the highest timestamp wins everywhere. Simple and convergent, but it silently discards the other write, and clock skew between servers can make an earlier write win. Acceptable for data where losing one of two near-simultaneous updates is fine (a profile's "last active" time); dangerous for anything additive.
  3. Merge the values. Keep both and combine — e.g. concatenate, take the union of sets, or keep siblings and let the application or the user pick (Amazon's Dynamo shopping cart merged carts; Riak keeps "siblings").
  4. CRDTs (Conflict-free Replicated Data Types). Data structures mathematically designed so that concurrent updates always merge to the same result regardless of order — no coordination needed.

6.3 CRDTs in one example: a like counter

Suppose likes on a post are counted in three regions. If each region stored a single number and replicated it with LWW, concurrent increments would be lost (US says 101, EU says 101 — the truth is 102). A G-Counter (grow-only counter) CRDT instead keeps one slot per region; each region increments only its own slot, and merging takes the per-slot maximum:

US state:   {US: 60, EU: 40, ASIA: 0}   (US got a like: US 59 → 60)
EU state:   {US: 59, EU: 41, ASIA: 0}   (EU got a like: EU 40 → 41)
merge = per-slot max → {US: 60, EU: 41, ASIA: 0}  → total = 101 ✔ (both likes counted)

Unlikes? Use a PN-Counter: two G-Counters (P for increments, N for decrements); value = sum(P) − sum(N).
Sets (e.g. "users who liked")? Use an OR-Set (observed-remove set): adds and removes carry unique tags,
so a concurrent add and remove resolve deterministically.

CRDTs are used in Riak, Redis Enterprise active-active, Azure Cosmos DB, collaborative editors (Yjs, Automerge) and some messaging systems. They're powerful but only exist for specific data shapes; arbitrary business invariants ("balance must not go negative") can't be guaranteed without coordination.

7. Leaderless (Dynamo-style) replication

Amazon's 2007 Dynamo paper popularised a design with no leader at all: the client (or a coordinator node acting for it) sends each write to several replicas in parallel and reads from several replicas in parallel. Cassandra, ScyllaDB, Riak and Voldemort work this way (DynamoDB, despite the name, uses leaders per partition internally).

7.1 N, W, R and the quorum condition

If R + W > N, the set of nodes you read from and the set that acknowledged the latest write must overlap in at least one node (pigeonhole principle), so the read sees the latest successful write. That's a quorum.

Writer W = 2 Reader R = 2 Replica Abio v2 ✔ Replica Bbio v2 ✔ (overlap) Replica Cbio v1 (stale) ✘ timed out v1, v2 → newest = v2 N = 3, W + R = 4 > 3 ⇒ every read set shares ≥ 1 node with every write set
Quorum read/write with N = 3, W = 2, R = 2. The write reached A and B; the read asked B and C. Because W + R > N, at least one node (B) in the read set has the newest version, so the reader returns v2 — and can repair C.

7.2 Worked quorum examples

ConfigR + W > N?Writes surviveReads surviveCharacter
N=3, W=2, R=24 > 3 ✅1 node down1 node downThe classic balanced quorum (Cassandra QUORUM/QUORUM).
N=3, W=1, R=12 > 3 ❌2 nodes down2 nodes downFastest, most available; reads may be stale (eventual). Good for likes, view counts.
N=3, W=3, R=14 > 3 ✅0 nodes down2 nodes downRead-optimised; any single failure blocks writes.
N=3, W=1, R=34 > 3 ✅2 nodes down0 nodes downWrite-optimised; any failure blocks reads.
N=5, W=3, R=36 > 5 ✅2 nodes down2 nodes downMore fault tolerance, more replicas to pay for.

General rule: a write tolerates N − W unavailable replicas and a read tolerates N − R. Latency is set by the W-th (or R-th) fastest replica, so quorums also cut tail latency: a slow node is simply not waited for.

Numeric example: N = 3, W = 2, R = 2, replica latencies 2 ms, 3 ms and 40 ms (one is in a GC pause). A quorum write completes at 3 ms (second fastest), not 40 ms. With W = 3 it would take 40 ms — and fail entirely if that node were down.

Even with R + W > N, edge cases remain: concurrent writes resolved by LWW can drop one; a write that succeeded on fewer than W nodes is reported as failed but isn't rolled back on the nodes that took it; and sloppy quorums (next) break the overlap guarantee. Quorums give strong-ish per-key behaviour, not linearizability or transactions.

7.3 Cassandra consistency levels

LevelMeaning (per read or write)Typical use
ONE / TWO1 / 2 replicas anywhere must respondLow-latency, stale-tolerant data
QUORUMMajority of all replicas across all DCs (e.g. 4 of 6)Strong reads in a single-DC cluster
LOCAL_QUORUMMajority of replicas in the coordinator's DC (2 of 3)The workhorse in multi-region: strong within a region, no cross-region wait
EACH_QUORUMA majority in every DC (writes)Rare; strong cross-DC writes at high latency
ALLEvery replicaAlmost never — any failure blocks the request

7.4 Sloppy quorums and hinted handoff

Suppose N = 3 and two of a key's three "home" replicas are unreachable. A strict quorum write (W = 2) fails. A sloppy quorum instead accepts the write on other healthy nodes that aren't among the key's N home nodes, so the system stays writable. Each stand-in node stores the write with a hint — "this belongs to node C" — and when C comes back, it forwards the data: hinted handoff. This raises write availability, but a read quorum from the home nodes may not see the write until handoff completes, so R + W > N no longer guarantees freshness. (Riak and Dynamo use sloppy quorums; Cassandra uses hinted handoff for temporarily down replicas, but hints don't count toward the consistency level except with ANY.)

7.5 Read repair and anti-entropy

Replicas that missed writes (down, overloaded, dropped hints) must eventually be fixed. Two mechanisms:

A Merkle tree (hash tree) has a hash of each key range at the leaves, and each parent is the hash of its children. Two replicas compare root hashes: if equal, the ranges are identical and they're done. If not, they descend only into children whose hashes differ — so they find the few differing ranges with a handful of hash exchanges instead of comparing every key.

          Replica A                                  Replica B
           root: 9f3c                                 root: 71ab        ← differ → descend
          /          \                               /          \
     h(L): 4e1d    h(R): 88a0                   h(L): 4e1d    h(R): 2c77  ← left equal: skip whole half
      /    \        /     \                      /    \        /     \
   r1:1a  r2:7b  r3:c0  r4:5e                 r1:1a  r2:7b  r3:c0  r4:d9  ← only range r4 differs
                                                                          → stream just r4's rows
 With 2^20 leaf ranges, finding one bad range takes ~20 levels of comparisons, not 1M.

Cassandra's nodetool repair builds Merkle trees per token range; operators run it regularly (more often than gc_grace_seconds, so deletions propagate before tombstones are purged). The same idea appears in Git, blockchains, and DynamoDB/Riak anti-entropy.

8. How the replication log is implemented

"The leader sends its changes to followers" hides an important choice: what exactly is sent?

MethodWhat's shippedProsConsUsed by
Statement-basedThe SQL statements themselves (UPDATE posts SET … WHERE …)Compact logNon-deterministic functions (NOW(), RAND(), UUIDs), auto-increments, triggers and statement ordering can make replicas divergeOld MySQL default (before 5.1); VoltDB (with deterministic procedures)
WAL shipping (physical)The storage engine's low-level log: "bytes X–Y of page 4711 changed"Exact byte-for-byte copy; no extra logging work; simple and fastTied to the storage format: leader and followers usually must run the same major version — upgrades without downtime are hard; can't replicate a subset of tablesPostgreSQL streaming replication, Oracle Data Guard (physical)
Logical (row-based)Row-level changes: "insert row {…} into likes", "update posts id=9 set like_count=102", with before/after imagesDecoupled from storage internals: cross-version upgrades, selective tables, readable by external systemsLarger logs for bulk updates; schema changes need careMySQL binlog (ROW format), PostgreSQL logical replication / logical decoding
Trigger-basedApplication-defined triggers copy changes into a log tableVery flexible (filtering, transforming)Overhead, fragilitySlony, Bucardo (legacy)
CDC (Change Data Capture)A logical change stream published to other systems, usually KafkaKeeps caches, search indexes and the warehouse in sync without dual writes; replayableAnother pipeline to operate; consumers are eventually consistentDebezium, Maxwell, DynamoDB Streams, MongoDB change streams

8.1 Change Data Capture in the Instagram architecture

PostgreSQLusers table
→logical decoding
CDC connectore.g. Debezium
→row events
Kafkatopic: users.changes
→consume
Consumerssearch indexer, cache invalidator, warehouse

CDC treats the database's own replication log as the single source of truth for "what changed". When Alice renames herself, the row change flows to the search indexer (update her document in Elasticsearch), a cache invalidator (delete her cached profile in Redis) and the data warehouse — all without the User Service writing to four systems and risking partial failure. See Message Queues & Kafka.

9. How Instagram uses replication

📸 What is publicly known

9.1 Putting it together: a profile edit and a profile view

  1. Alice (home shard's primary in US-East) edits her bio in Singapore. The User Service in the Asia region forwards the write to the shard's primary in US-East (~200 ms round trip; edits are rare, so this is acceptable).
  2. The primary commits and ships WAL to its same-region synchronous standby (zero data loss on failover) and asynchronously to replicas in Europe and Asia.
  3. The service records "Alice wrote at LSN X" (or simply "Alice wrote at time T"). For the next few seconds, Alice's own profile reads go to the primary or to an Asia replica that has replayed past LSN X — read-your-writes.
  4. Everyone else viewing Alice's profile in Asia reads from the local replica (~2 ms). If it's 300 ms behind, they see the old bio for 300 ms — nobody notices.
  5. A CDC/event consumer updates the search index and invalidates the cached profile in every region.

9.2 Cassandra writes for likes and feed entries

A like is written to Cassandra in the user's local region with LOCAL_QUORUM (2 of 3 local replicas, a few ms), and Cassandra replicates it asynchronously to the other regions' 3 replicas each. Reads of like lists use LOCAL_ONE or LOCAL_QUORUM depending on how fresh they must be. Conflicts are rare because likes are insert-only keyed by (post_id, user_id) — inserting the same like twice is idempotent, so last-write-wins is harmless here.

Rough numbers (assumptions)

Assume a User DB shard: 20,000 reads/s and 200 writes/s at peak.
One server comfortably handles ~5,000 reads/s for this query mix  → need ≥ 4 read replicas + headroom
→ primary + 1 sync standby (US-East) + 2 async replicas per region × 3 regions.

Cassandra storage with RF = 3 in each of 3 regions = 9 copies:
  100 TB of unique data → 900 TB of disk (before compaction overhead).
  Durability is excellent, but replication factor is a direct cost multiplier.

WAL volume: 200 writes/s × ~2 KB of WAL (row + indexes) ≈ 400 KB/s per shard
  → trivial bandwidth, but a 50 GB backfill job generates 50 GB of WAL → minutes of replica lag.

10. Trade-offs & common pitfalls

Single-leaderMulti-leaderLeaderless
Write pathOne leader per partitionAny leader (per region)Any W of N replicas
ConflictsNone (one writer)Yes — need resolutionYes — LWW / version vectors / CRDTs
Write latency across regionsHigh for remote usersLow (local leader)Low with local quorums
FailoverNeeded; risky (split brain, lost writes)Other leaders keep goingNot needed — no leader
ConsistencyStrong on leader; eventual on followersEventualTunable per request
ComplexityLowestHighMedium (repairs, tombstones)
ExamplesPostgreSQL, MySQL, MongoDB, Redis, KafkaMySQL/Postgres multi-master tools, CouchDB, Cosmos DB multi-region writesCassandra, ScyllaDB, Riak, Voldemort

✅ What replication gives you

  • High availability — survive node, rack, zone and region failures
  • Read throughput that scales with the number of replicas
  • Low-latency reads near users
  • Durability beyond a single disk; a base for backups and analytics replicas

⚠️ What it costs

  • Stale reads and the lag anomalies above
  • Failover complexity: split brain, lost writes, flapping
  • Conflict resolution for multi-leader / leaderless
  • Storage and network cost × replication factor
  • Does not scale writes — every replica applies every write (that's sharding's job)

Common pitfalls

11. Real-world technologies

TechnologyReplication modelWhen to use it
PostgreSQL streaming replicationSingle-leader, WAL shipping; sync/async/quorum-sync; logical replication for selective/cross-versionRelational core with read replicas (Instagram's User DB).
Patroni (+ etcd/Consul/ZooKeeper)HA manager: leader election with a TTL'd leader key, automatic failoverAutomatic PostgreSQL failover without split brain.
MySQL (binlog, GTID, semi-sync), OrchestratorSingle-leader; row-based binlog; semi-sync plugin; topology managerMySQL fleets (Meta, GitHub, Shopify).
MySQL Group Replication / GaleraMulti-primary with certification-based conflict detectionSynchronous-ish multi-writer clusters in one region.
Cassandra / ScyllaDBLeaderless, tunable consistency, multi-DC, hinted handoff, read repair, Merkle repairHigh-write data across regions (Instagram feeds/activity).
MongoDB replica setsSingle-leader with Raft-like elections; write concern / read preferenceDocument data with automatic failover.
Redis replication + Sentinel / ClusterAsync single-leader per shard; Sentinel for failoverCaches and timelines (can lose recent writes on failover).
KafkaPer-partition leader with in-sync replicas (ISR); acks=all + min.insync.replicas=2Durable event logs.
Spanner, CockroachDB, etcdConsensus (Paxos/Raft) — majority commit, built-in leader leases and termsStrongly consistent replication without manual failover logic.
DebeziumCDC from DB logs into KafkaFeeding search, caches, warehouses from a source-of-truth DB.

12. Interview corner

Synchronous or asynchronous replication for Instagram's user database?

Both, layered. One synchronous (or semi-sync) standby in the same region so a failover loses no acknowledged writes (RPO 0) at the cost of ~1 ms extra write latency; asynchronous replicas in the same and remote regions for read scaling and disaster recovery, since cross-region synchronous replication would add 100+ ms to every write and make writes depend on a remote region's health. With quorum-sync (ANY 1 of two local standbys), one standby failing doesn't block writes.

A user updates their profile picture but still sees the old one after refresh. Why, and how do you fix it?

The write went to the leader, the read hit an async replica that hadn't applied it yet (or a cache that wasn't invalidated). Provide read-your-writes: route that user's reads of their own profile to the leader for a short window after a write (e.g. 10 s), or track the commit LSN in the session and only read from replicas that have replayed past it; invalidate/update the cache on write; on the client, show the new picture optimistically. Avoid sending all reads to the leader — it defeats read scaling.

Explain R + W > N with an example. Does it guarantee linearizability?

With N = 3 replicas, W = 2 and R = 2, any 2 nodes that acknowledged a write and any 2 nodes that answer a read must share at least one node, so the read sees the newest version (by timestamp/version) and the client returns it. It tolerates one unavailable node for both reads and writes. It does not guarantee linearizability: concurrent writes with LWW can lose data, sloppy quorums break the overlap, a partially-failed write may be visible to some reads and not others, and without synchronous read repair two sequential readers can see new-then-old values.

What is split brain and how do you prevent it?

Two nodes both believe they're the leader and accept writes, so data diverges — typically after a network partition or a long pause on the old leader. Prevent it with a majority-based election (only the side with a quorum can have a leader), time-bounded leader leases that the old leader stops using before they expire, STONITH, and most robustly fencing tokens: a monotonically increasing epoch attached to every write, with storage rejecting stale epochs. Consensus systems (Raft terms, Patroni's etcd key) do this for you.

When would you choose multi-leader replication, and how would you handle conflicts?

When users in several regions must write with low latency and regions must keep accepting writes during inter-region partitions, or for offline clients. First choice is conflict avoidance: give each user a home region and route their writes there, so a record has one writer at a time. For data that can be written anywhere, use LWW only where losing a concurrent update is harmless, CRDTs for counters/sets (PN-counter for likes, OR-set for membership), and application-level merges elsewhere. Never use multi-leader for invariants like unique usernames — those need a single authority.

How do Cassandra replicas that missed writes get fixed?

Three layers: hinted handoff — the coordinator stores writes for a temporarily down replica and replays them when it returns (for a limited window); read repair — when a read at a consistency level > ONE sees differing versions, the newest is written back to stale replicas; and anti-entropy repair (nodetool repair) — replicas build Merkle trees per token range, compare hashes top-down and stream only the differing ranges. Repair must run within gc_grace_seconds so tombstones propagate before they're purged, or deleted data can reappear.

Statement-based vs row-based vs WAL-shipping replication?

Statement-based ships SQL text: compact, but non-deterministic statements (NOW(), RAND(), auto-increment, triggers) can diverge replicas. WAL shipping ships physical page changes: exact and cheap, but couples replicas to the same storage format/version and can't filter tables. Logical/row-based ships row images: version-independent, selective, and consumable by other systems — which is what enables CDC into Kafka, search indexers and warehouses.

13. Check your understanding

N = 5. Which (W, R) pairs give quorum overlap: (3,3), (2,3), (4,2), (1,5)?

Need W + R > 5: (3,3) → 6 ✅; (2,3) → 5 ❌ (equal is not enough); (4,2) → 6 ✅; (1,5) → 6 ✅ (but any single failure blocks reads). Writes tolerate N − W failures, reads N − R.

With N = 3, W = 2, replica write latencies are 1 ms, 4 ms and 90 ms. What's the write latency? What if W = 3?

W = 2 → the 2nd fastest ack → 4 ms. W = 3 → 90 ms, and the write fails entirely if that slow node is down. Quorums hide stragglers.

Bob sees a new comment on refresh, then it vanishes on the next refresh. Which guarantee is violated and what's a cheap fix?

Monotonic reads. The two reads went to replicas with different lag. Cheap fix: sticky routing — choose the replica by hash(user_id) so each user always reads from the same replica (fail over only if it dies).

The leader acknowledged 300 writes in the last second before crashing; replication was asynchronous with 400 ms lag. Roughly how many writes may be lost after failover?

Anything not yet shipped to the promoted follower: about 400 ms worth ≈ ~120 writes (300/s × 0.4 s). That is the RPO of async replication. A semi-sync local standby would make it 0.

Two regions concurrently increment a like counter stored as one integer with last-write-wins. Start: 500. What can happen, and what fixes it?

Both read 500 and write 501; LWW keeps one → 501 instead of 502 (a lost update across regions). Fix with a CRDT counter (per-region slots, merge by max, sum for the value), or route all increments for a post to one region / one counter service.

Why can't you add read replicas to fix a database that is overloaded with writes?

Every replica must apply every write, so write load is copied, not divided. Replicas scale reads only. To scale writes you must partition the data — sharding — so each server handles a fraction of the writes.

An old leader wakes from a 40 s GC pause with token 17 and writes to storage that has already seen token 18. What happens and why is this better than trusting a lease?

Storage rejects the write because 17 < 18 (fencing). A lease alone relies on the old leader noticing its lease expired — but a paused process can't notice anything until it resumes, and it may act on stale belief immediately. Fencing enforces safety at the resource, independent of the old leader's clock or behaviour.

🎯 Key takeaways

Related lessons