Start here: one machine is a single point of failure#
TL;DRthe 30-second version
- Keep live copies of the data on other machines. A leader takes writes and ships its change log to followers, which replay it and end up with an identical copy.
- Two knobs decide the behaviour. Topology: who accepts writes β one machine (single-leader), several (multi-leader), or any replica (leaderless). Timing: how long the write waits for copies before it's acknowledged β synchronous, asynchronous, or semi-synchronous.
- Async acks the instant the leader writes locally, so it's fast β but if the leader dies before a follower caught up, acknowledged writes are lost. Sync waits for a follower to confirm, so nothing is lost β but every write pays a network round-trip and stalls if a follower is slow.
- Semi-sync is the middle: wait for one follower to confirm, then ack. One round-trip of safety without betting on every replica.
- Copies always run a little behind the leader β replication lag. That lag is what causes stale reads, and the fixes are read-after-write and monotonic-read routing.
Put your whole database on one machine and you've built two failures into it. The first is obvious: that machine is a single point of failure, so when its disk dies at 3am, every read and every write stops until someone restores a backup β and a restore is often an hour of downtime, on data that was already hours stale. The second is quieter: one machine has a fixed amount of CPU and memory, so once reads saturate it, there's nowhere to put the next reader. You can't scale reads on a box that's already full.
The obvious first fix is a nightly backup copied to a second machine. It fails on both counts. A backup taken at midnight is useless for the writes that landed at 2am β they're simply gone β and promoting a cold backup to serve traffic takes long enough that the outage is real. What you actually need is a second machine that follows the first continuously, staying within milliseconds of it, ready to take over the instant the first one dies and ready to answer reads the whole time. That machine is a replica, and keeping it current is the problem this page is about.
How a copy stays current: ship the log#
The naive way to keep a copy current is to re-send the whole database every so often. On anything past a few gigabytes that's hopeless β you'd spend all your bandwidth copying rows that didn't change. So don't send the data; send the changes. A database already writes every change to an ordered, append-only file before it touches the real tables β the write-ahead log (WAL), the same log the /wal/sim topic builds. Each entry says exactly what changed: "set row 42's balance to 90", in order. That log is the perfect thing to replicate.
The mechanism is: the leader appends each change to its log and streams those entries to every follower; each follower replays them in the same order and lands in the same state. Because the log is ordered and every follower applies it in that order, the copies are deterministic β feed two followers the same log and they end up identical. This is replication, and log shipping is how nearly every single-leader system does it (PostgreSQL calls it streaming replication; MySQL ships its binary log; MongoDB ships an oplog). Raft, over in /raft/sim, is this same idea with an election bolted on to pick the leader safely.
The timing dial: sync, async, semi-sync#
Now the sharp question. A write lands on the leader. The leader appends it to its log and applies it locally. Before it tells the client "committed", does it wait for the followers to have the write too, or not? That single choice is the timing dial, and it trades durability against latency.
Asynchronous replication doesn't wait. The leader writes locally, acknowledges the client immediately, and streams the log to followers in the background. Writes are as fast as a single machine β no network round-trip on the write path. The cost hides in one word: the client was told "done" before any follower had the write. If the leader's disk dies in that gap, the write is gone, even though the client believes it succeeded. Acknowledged is not the same as safe.
Synchronous replication waits. The leader ships the write to a follower and holds the client's request open until the follower confirms it has the write on disk, then acknowledges. Now nothing acknowledged can be lost β there are always at least two copies of every committed write. The cost is also one thing: every single write now pays a network round-trip to the follower, and if that follower is slow or down, the write can't complete. Wait for all your followers and one sick replica stalls every write in the system.
Semi-synchronous replication is the middle ground, and it's what most durable setups actually run. The leader waits for one follower to confirm β not all of them β then acknowledges. You get the guarantee that every acknowledged write exists on at least two machines, but you only wait for the fastest follower to reply, not the slowest. One round-trip of safety, and a single slow replica doesn't stall you because the others can supply the confirmation.
PredictYou run one leader and five followers. Waiting for all five to confirm means every write waits on the slowest of five replicas. Semi-sync waits for just one confirmation. Which follower's latency does each mode pay, and why does that make semi-sync the common default?
Hint: Full-sync's latency is set by the slowest replica; semi-sync's by the fastest that answers.
Full synchronous replication pays the slowest of the five, because it can't acknowledge until every required follower has confirmed β so one follower doing garbage collection or on a congested link sets the latency of every write, and one dead follower stalls writes entirely. Semi-sync pays the fastest follower that answers: the leader needs one confirmation, so it takes whichever replica replies first and ignores the stragglers. That gives you the same durable guarantee (the write is on at least two machines) while cutting the tail latency and removing the dependency on any specific replica being healthy. That combination β durable enough, but not hostage to the slowest node β is why semi-sync is the default shape for production primary-replica databases.
The topology dial: who accepts a write#
The second dial is topology: how many machines are allowed to accept a write. There are three answers, and they climb in both power and difficulty.
Single-leader (also called primary-replica or primary-backup) is what we've described: one machine takes every write, the followers are read-only copies. Its great virtue is simplicity β writes are ordered by one machine, so there are never two conflicting versions of a row to reconcile. Every mainstream relational database defaults to this: PostgreSQL, MySQL, and MongoDB replica sets all have exactly one writable primary at a time. The limits are equally clear: all writes fund a single machine (you scale reads, not writes), and every write from a distant client crosses the network to that one leader.
Multi-leader (multi-master) lets more than one machine accept writes β typically one leader per datacenter, each streaming its log to the others. Now a user in Frankfurt writes to the Frankfurt leader at local speed, and if a whole datacenter goes offline the others keep taking writes. The price is severe: two leaders can accept conflicting writes to the same row at the same time β Frankfurt sets a field to "A" while Virginia sets it to "B" β and the system must later decide which wins. That conflict resolution is genuinely hard, and it's why multi-leader is reserved for cases that truly need multi-region writes. The /crdts topic is one principled way to make those concurrent writes merge without a lost update.
Leaderless (Dynamo-style) drops the leader entirely. The client sends each write to several replicas at once and reads from several at once. There's no promotion, no failover, no single writable node to lose. The trick that makes reads correct is the quorum rule from /quorum/sim: with N replicas, if every write lands on W of them and every read consults R of them, then setting W + R > N forces the read set and the write set to overlap by at least one replica β so a read always touches at least one copy that has the latest write. Cassandra, DynamoDB, and Riak work this way, letting you tune W and R per query to trade consistency against availability.
| Topology | Who writes | Conflicts? | Real systems |
|---|---|---|---|
| Single-leader | One primary at a time | None β one machine orders all writes | PostgreSQL, MySQL, MongoDB |
| Multi-leader | One leader per region | Yes β needs conflict resolution | Multi-region MySQL/Postgres, CouchDB |
| Leaderless | Any replica (client picks W) | Avoided by quorum overlap (W+R>N) | Cassandra, DynamoDB, Riak |
What it costs: replication lag and stale reads#
Every mode that doesn't wait synchronously shares one cost: a follower is always a little behind the leader. This gap is replication lag β the time between a write committing on the leader and that same write being applied on a follower. Under light load it's milliseconds. Under a burst of writes, or during a big batch update, a follower can fall seconds or even minutes behind while it works through the backlog. And whenever you read from a lagging follower, you get old data β a stale read.
Stale reads cause three specific, named anomalies. Each has a concrete fix, and interviewers ask for both.
- Read-after-write (also read-your-writes): you post a comment, the page reloads and reads a follower that hasn't got your write yet, and your own comment is missing β it looks like the write failed. The fix is to route a user's reads to the leader for a short window after they write, so people always see their own writes.
- Monotonic reads: you refresh, see a comment, refresh again and it's gone β because the two refreshes hit two different followers with different lag, and the second one was further behind. Time appeared to move backwards. The fix is to pin each user to one replica (by hashing their user id), so their reads never jump to a more-stale copy.
- Consistent-prefix reads: an observer sees an answer before the question, because two writes that had an order on the leader arrived on the follower out of order. The fix is to make sure causally-related writes go through the same partition, preserving their order.
PredictYour leader acknowledges writes locally in 1 ms and replicates asynchronously; under load a follower runs 500 ms behind. The leader's disk fails and a follower is promoted. At 5,000 writes/second, roughly how many acknowledged writes are gone β and what's the general formula?
Hint: Everything the leader acked but hadn't shipped yet is the loss. Multiply the lag window by the write rate.
About 2,500 writes. Every write the leader acknowledged in the last 500 ms was confirmed to a client but hadn't reached any follower yet, so promoting a follower loses all of them: 0.5 s Γ 5,000 writes/s = 2,500 acknowledged-but-lost writes. The general formula is data-loss window β replication lag Γ write rate. This is the number that makes async's danger concrete β the loss isn't "a write or two", it scales with both how far behind your followers run and how fast you're writing, and every one of those writes was reported successful to a user. It's also the exact number synchronous and semi-synchronous replication drive to zero, by refusing to acknowledge until a second machine has the write.
Where it breaks: failover can lose acknowledged writes
Failover β the very mechanism a single-leader system relies on to survive a leader's death β is also its most dangerous moment. When the leader dies, some follower has to be promoted to the new leader. With asynchronous replication, the follower being promoted is behind β it's missing the writes the old leader acknowledged but hadn't shipped yet. Those writes don't come back. The new leader simply never had them, and it starts taking new writes as if they never existed.
It gets worse when the old leader returns. It comes back holding writes that no other machine ever saw β writes it had acknowledged to clients. But there's a new leader now, and the cluster's history has moved on without those writes. The standard resolution is to discard them: the recovering machine rolls back its extra writes to rejoin as a follower. MongoDB names this exactly β a rollback β and it's why a write acknowledged with the default w:1 can vanish if the primary fails before replicating it. Requiring w:"majority" (wait for a majority of members) closes the hole, at the cost of the extra wait.
- Split-brain: if a network partition lets two machines both believe they're the leader, both take writes and you get two divergent histories. Real systems prevent this with a majority requirement (a leader must hold a quorum of votes) β the same quorum idea from /quorum/sim, and the reason Raft ties leadership to a majority election.
- Failover on lag: promoting a badly-lagged follower maximises data loss. Good tooling promotes the most caught-up follower, and semi-sync guarantees at least one follower is current.
- The semi-sync timeout trap: a semi-sync leader that can't reach any follower falls back to async (MySQL does this after 10 seconds by default) β so during a partial outage you may be running async precisely when you can least afford it. Monitor for the fallback.
Choosing a mode
The two dials are independent, so a real system is a pair: a topology and a timing. The timing dial is the one with a clean rule of thumb, because it's a pure durability-versus-latency trade.
| Timing | Leader waits for⦠| Data loss on leader death | Write latency |
|---|---|---|---|
| Asynchronous | Nothing β acks locally | Yes β the whole lag window | Lowest (one machine) |
| Semi-synchronous | One follower to confirm | None (β₯2 copies acked) | One round-trip, to the fastest follower |
| Synchronous (all) | Every sync follower | None | Highest β the slowest follower + stalls if any is down |
- Can't lose an acknowledged write (payments, orders, ledgers): semi-sync at least, so every ack is on two machines. Pure async is disqualified β its failover loses acked writes.
- Latency-critical and can tolerate rare loss (analytics events, view counts, activity feeds): async. The write path stays single-machine fast, and losing the last half-second of view counts in a rare failover is acceptable.
- Read-heavy web app: single-leader with a fleet of async read replicas, plus read-after-write routing (send a user's reads to the leader briefly after they write) so users always see their own actions.
- Multi-region writes that must survive a region outage: multi-leader or leaderless β and budget for conflict resolution or quorum tuning, which is the real cost you're taking on.
The whole decision, in one place
The two questions from the top, with the answer each system gives and what it buys:
| If you need⦠| Topology | Timing | Because |
|---|---|---|---|
| Simple, strong ordering, scale reads | Single-leader | Semi-sync | One writer means no conflicts; semi-sync makes every ack durable without slowest-node stalls |
| Absolute lowest write latency, loss OK | Single-leader | Async | Acks on local write; never waits on the network |
| Survive a whole region going down | Multi-leader | Async between regions | Local writes per region; conflicts resolved after the fact |
| Tunable per-query consistency, no failover | Leaderless | Quorum (W+R>N) | No leader to lose; overlap of read and write sets supplies the latest value |
Notice that leaderless folds the two dials into one: instead of a fixed timing, you set W (how many replicas a write waits for) and R (how many a read consults) per query, and their sum against N is your consistency. That's why Dynamo-style stores describe themselves as tunable β the /quorum/sim topic is exactly this knob.
In the wild
- PostgreSQL β single-leader streaming replication. synchronous_commit dials the timing per transaction: off/local (async), on (wait for a standby to flush to disk), remote_apply (wait until the standby has applied it, so a read there is current). synchronous_standby_names picks which standbys count.
- MySQL β single-leader, ships the binary log. Semi-sync (rpl_semi_sync) waits for one replica to acknowledge, with a timeout (default 10s) that falls back to async β chosen for durable-enough replication without stalling on a slow replica.
- MongoDB β a replica set is single-leader with an oplog. Write concern is the timing dial: w:1 acks on the primary alone (can roll back on failover), w:"majority" waits for a majority so acknowledged writes can't be rolled back.
- Cassandra / DynamoDB / Riak β leaderless, Dynamo-style. N replicas per key; the client picks a consistency level (ONE, QUORUM, ALL) per read and per write, which sets R and W, and W+R>N is the rule for reading the latest value.
- Raft-backed systems (etcd, Consul, CockroachDB) β single-leader by election, and the leader commits a log entry once a majority of replicas have it. That majority-commit is effectively semi-sync built into the consensus layer.
Common questions & gotchas
Isn't synchronous replication just strictly safer, so why not always use it?
Safer for durability, but it couples your write latency and availability to your followers. Every write waits a network round-trip, and if you wait for all followers, a single slow or dead replica stalls or blocks every write in the system. That's why full-sync-to-all is rare and semi-sync (wait for one) is the common durable default β you get two copies of every ack without being hostage to the slowest node.
Does semi-sync mean my write is safe even if the leader dies right after acking?
Yes, as long as semi-sync was actually active. The point of waiting for one follower is that an acknowledged write is on at least two machines, so a promoted follower has it. The catch is the timeout fallback: if the leader couldn't reach a follower and dropped to async (MySQL after 10s by default), a write acked during that window has the async guarantee, not the semi-sync one. Monitor for the fallback.
Why does my app sometimes not see a write it just made?
You wrote to the leader and then read from a follower that hadn't replicated the write yet β a read-after-write violation caused by replication lag. Fix it by routing a user's reads to the leader for a short window after they write, or by reading from a replica you've confirmed is caught up past your write's position.
Single-leader can't scale writes. When does that actually bite?
When your write volume exceeds what one machine can commit, since every write funds the single leader (followers only replay). Read-heavy apps rarely hit it β you scale reads by adding followers. Write-heavy or multi-region-write workloads do, and that's the point where you move to multi-leader or leaderless and take on conflict resolution or quorum tuning as the cost.
How is this different from sharding?
They're orthogonal and usually combined. Replication makes copies of the same data for durability and read scaling; sharding (the /sharding topic) splits different data across machines to scale writes and storage. A real large system shards the data into partitions and then replicates each partition β sharding decides which machine owns a key, replication decides how many copies of it exist.
QuizA payments team runs a single-leader database with asynchronous replication and automated failover. After a leader crash, a handful of confirmed payments have vanished. What's the root cause and the minimal fix?
- The followers were sharded wrong; re-shard the data.
- Async acked writes before any follower had them, so the promoted follower was missing the last writes; switch to semi-sync so every ack is on two machines.
- Reads were hitting stale followers; add read-after-write routing.
- The quorum was misconfigured; set R + W > N.
Show answer
Async acked writes before any follower had them, so the promoted follower was missing the last writes; switch to semi-sync so every ack is on two machines. β With async replication the leader acknowledges a write the instant it commits locally, before any follower has it. When the leader crashed, those acknowledged-but-unreplicated writes existed nowhere else, so the promoted follower β which was behind β came up without them, and the confirmed payments were lost (the data-loss window = lag Γ write rate). The minimal fix is semi-synchronous replication: wait for one follower to confirm before acknowledging, so every acknowledged write is on at least two machines and a promoted follower is guaranteed to have it. Read-after-write routing fixes stale reads, not lost writes; quorum (R+W>N) is a leaderless concept, not applicable to this single-leader setup; and sharding is unrelated.
In an interview
Replication is a favourite because it rewards structure over trivia. Don't list settings β frame it as the two dials (who writes, how long it waits) and reason from there. The trap the interviewer is watching for is treating "acknowledged" as "durable": name the async failover data-loss window before they ask, and you've shown you understand the trade, not just the vocabulary.
- Lead with the two dials: topology (single-leader / multi-leader / leaderless) and timing (sync / async / semi-sync). Everything else hangs off those two.
- State the core trade in one line: async is fast but loses acknowledged writes on failover; sync loses nothing but pays a round-trip and stalls on a slow follower; semi-sync waits for one follower β durable and not hostage to the slowest.
- Name the lag anomalies and their fixes: read-after-write (route recent writers to the leader), monotonic reads (pin a user to one replica). This is the detail that signals real experience.
- Compute the async data-loss window out loud: lag Γ write rate. It turns "you might lose writes" into a number, and it's the argument for semi-sync on anything that can't lose an ack.
- Ground it in systems: Postgres synchronous_commit, MySQL semi-sync + its timeout fallback, MongoDB w:1 vs w:"majority" rollbacks, Cassandra's tunable W/R/N. Naming the exact knob is what separates a strong answer.
PredictThe interviewer says: 'We're on a single Postgres primary with async read replicas. Users complain that right after they save a profile change, the page shows the old profile. What's happening, and what would you change β without hurting read scaling?'
Hint: Which replica did the reload read, and how do you fix it for just the writer without sending everyone to the primary?
The old profile is a read-after-write violation from replication lag: the save went to the primary, but the immediate reload read an async replica that hadn't replayed the change yet, so it returned the pre-save row. The wrong move is to make all reads hit the primary β that throws away the read scaling the replicas exist for. The right move is targeted: for a short window after a user writes, route that user's reads to the primary (or to a replica you've confirmed has replayed past their write's log position), while everyone else keeps reading from replicas. That preserves read scaling for the 99% of traffic that isn't reading its own just-made write, and fixes the anomaly exactly where it occurs. If they push further, mention monotonic reads β pin each user to one replica so a later read never lands on a more-lagged copy and appears to go backwards β and Postgres's remote_apply, which only acks once a standby has applied the write, if a subset of writes truly needs read-your-writes from the replica itself.
References & further reading
- Designing Data-Intensive Applications, Ch. 5 (Replication) β Kleppmann on leaders/followers, sync vs async, replication lag, and the read-after-write / monotonic-read anomalies.
- PostgreSQL β Streaming replication & synchronous_commit β The exact timing dial: off/local/remote_write/on/remote_apply and synchronous_standby_names.
- MySQL β Semisynchronous Replication β Wait-for-one-replica semantics, the AFTER_SYNC lossless wait point, and the timeout fallback to async.
- MongoDB β Write Concern & Replica Set Rollbacks β How w:1 writes can roll back on failover and why w:"majority" prevents it.
- Dynamo: Amazon's Highly Available Key-value Store (2007) β The leaderless / quorum (N, W, R) model behind Cassandra, DynamoDB, and Riak.