Part 3 · System design · Data, caching and scale
Replication and quorums
Keep N copies. Write to W of them, read from R. If R + W > N, every read overlaps the latest write.
How it works
With N = 3, W = 2, R = 2, any 2 replicas you read share at least one with the 2 that took the write, so taking the highest version gives the newest value. W = 1 and R = 1 is faster, but a read can hit a replica that missed the write. Leader and follower databases like Postgres and MySQL face the same trade: replicas lag, so reading your own write needs the primary, or a replica that has caught up.
| R + W > N | reads see the latest write |
| R + W <= N | faster, may read stale |
| W = N | one slow replica slows every write |
| replica lag | usually milliseconds, can be seconds under load |
Reach for it when
- Explaining consistency choices in any design
- Read after write bugs: the user saves, refreshes, sees the old value
- Deciding which reads may go to replicas
Implementation (JavaScript, tested)
// N replicas. A write lands on W of them, a read asks R of them and keeps the highest version.
// R + W > N means every read set overlaps every write set, so reads see the latest write.
class QuorumStore {
constructor(n, w, r) {
this.reps = Array.from({ length: n }, () => new Map());
this.w = w;
this.r = r;
this.version = 0; // ponytail: one global counter; real stores use per key versions or vector clocks
}
write(key, val, pick) {
// pick(n, k) says which k replicas answered; in real life the network decides
const v = { val, version: ++this.version };
for (const i of pick(this.reps.length, this.w)) this.reps[i].set(key, v);
}
read(key, pick) {
let best;
for (const i of pick(this.reps.length, this.r)) {
const v = this.reps[i].get(key);
if (v && (!best || v.version > best.version)) best = v;
}
return best?.val;
}
}Traps
- Quorums are not transactions. Two writers can still conflict; last write wins silently drops one of them.
- Sloppy quorums and hinted handoff weaken the guarantee during failures.
- Reads from a replica right after a write: send that user's reads to the primary for a short while, or wait for the replica to reach the write's position.
In real systems: Cassandra and DynamoDB let you choose per request (QUORUM, ONE, strongly consistent reads). Kafka uses acks=all with min.insync.replicas. Postgres has synchronous_commit and synchronous standbys.
Say out loud: With N copies, R + W > N guarantees overlap, so reads see the latest write. Lowering R or W buys latency with staleness, and I'd say which reads can live with that.