Replication is the practice of keeping copies of the same data on multiple physical machines connected via a network. In system design, replication achieves three non-negotiable objectives:
- High Availability (HA): Ensuring the system continues operating without downtime when individual machines, disks, or whole datacenters fail.
- Read Scalability: Scaling query throughput beyond the capacity of a single machine by distributing read requests across multiple replicas.
- Geographic Locality: Storing data physically close to users across continents to minimize speed-of-light network round-trip latency.
However, replication forces engineers to confront the fundamental realities of distributed systems: network latency, packet loss, clock drift, and partial network partitions.
1. Problem and Motivation
Consider a user updating their profile picture on a social platform. The write is committed to a primary database in North America. Moments later, the user refreshes their profile page.
Depending on how replication is architected:
- If the read hits an asynchronous European replica that is lagging by , the user sees their old profile picture, assumes their upload failed, and re-submits the image five times (Violation of Read-Your-Own-Writes).
- If the user reloads again and is routed to a different replica that is lagging even further behind, their data appears to move backward in time (Violation of Monotonic Reads).
- If two users concurrently edit the same document in an active-active multi-datacenter deployment, both writes succeed locally. When the datacenters exchange updates across the transatlantic fiber, their changes collide (Replication Conflict).
To build reliable architectures, engineers must choose between three foundational replication paradigms: Single-Leader, Multi-Leader, and Leaderless Quorums.
2. Mental Model & Architectural Taxonomy
1. Single-Leader Serialization & Replication Lag
Primary node accepts all client writes, serializing into WAL. Synchronous replica confirms before ACK (RPO=0); asynchronous replica replicates with lag.
2. Multi-Leader Active-Active & Conflict Detection
Leaders in Region 1 and Region 2 accept concurrent local writes without cross-region locks. Conflicts across the WAN are resolved via Version Vectors or CRDTs.
3. Leaderless Quorum Write (W=2 of N=3)
Client issues write v2 directly to all 3 replicas. Node 1 and Node 2 write to local storage and respond OK. With W=2, write is considered durable.
4. Quorum Read (R=2) & Read Repair
Client reads from Node 2 (v2) and Node 3 (stale v1). Client returns v2 with highest version timestamp. Coordinator dispatches async Read Repair to update Node 3.
| Replication Model | Primary Systems | Write Path | Conflict Handling | Best For |
|---|---|---|---|---|
| Single-Leader (Primary-Backup) | PostgreSQL, MySQL, Redis, Kafka, MongoDB | All writes go to one Leader; Leader streams WAL to Followers | None (Leader provides total write order) | ACID transactions, financial ledgers, strict ordering |
| Multi-Leader (Active-Active) | PgLogical / BDR, CouchDB, DynamoDB Global Tables | Writes accepted by local datacenter Leader; Leaders replicate asynchronously | Mandatory: LWW, CRDTs, Version Vectors, or manual merge | Multi-region active-active apps, collaborative docs |
| Leaderless (Dynamo-Style) | Apache Cassandra, Amazon DynamoDB, ScyllaDB | Client or Coordinator writes directly to replicas in parallel | Quorum intersection () + Read Repair | High-volume writes, time-series, metrics, 100% uptime |
Architectural Clarification (Multi-Leader vs. Distributed SQL): Do not confuse asynchronous Multi-Leader replication with NewSQL databases like CockroachDB, Google Spanner, or TiDB. Those systems partition data into fine-grained ranges where each range is governed by a synchronous Multi-Raft or Multi-Paxos single-leaseholder consensus group—preventing write conflicts upfront rather than reconciling divergent leaders after the fact.
3. Core Terminology & Replication Guarantees
- Replication Lag: The time delay between a write committing on the leader and that mutation becoming visible on an asynchronous follower.
- Read-After-Write Consistency (Read-Your-Own-Writes): A guarantee that if a user reloads a page after submitting an update, they are guaranteed to see their own update.
- Monotonic Reads: A guarantee that once a user has seen a particular state of data, they will never subsequently observe an older state.
- Consistent Prefix Reads: A guarantee that if a sequence of writes occurs in a certain causal order, anyone reading those writes will see them appear in that same order.
- Split-Brain: A catastrophic condition where a network partition isolates the leader, causing followers to elect a second leader. Both nodes simultaneously accept conflicting writes without knowing about each other.
- Fencing Token: A monotonically increasing number issued by a consensus service (e.g., etcd or ZooKeeper) ensuring that an old leader cannot execute writes if it wakes up from a network pause.
4. Deep Dive: Leaderless Quorum Mechanics ()
In a leaderless system (pioneered by Amazon’s Dynamo paper):
- The cluster defines a Replication Factor (): the number of distinct nodes that must store a copy of any given data item.
- For every write, the client (or coordinator) sends the update to all replicas and waits for successful acknowledgments before marking the write complete.
- For every read, the client queries all replicas and waits for responses before returning the result with the highest version number or timestamp.
4.1 The Pigeonhole Principle of Quorums
The strict quorum condition requires:
Why does this guarantee consistency? Suppose .
- nodes receive the latest write (Version 2).
- When a client reads from nodes, the sum of read and write nodes is .
- Because , by Dirichlet’s Box (Pigeonhole) Principle, the set of read nodes and the set of write nodes must overlap by at least one node:
At least one of the 3 queried nodes is guaranteed to return Version 2.
Important Nuance ( vs. Strict Linearizability): Satisfying guarantees quorum read/write set overlap for completed writes, ensuring at least one read responder holds the latest completed version. However, basic leaderless LWW quorums are still weaker than strict linearizability: if a write is concurrently in flight (only partially applied across replicas) or if two concurrent clients read during an ongoing write without synchronous read-repair before returning, one client can observe the new value while a subsequent client observes the old value. Achieving true linearizability over quorums requires synchronous read-repair (the ABD algorithm) or consensus (covered in
CORE-M20-001).
Replicas: [ Node 1 ] [ Node 2 ] [ Node 3 ] [ Node 4 ] [ Node 5 ]
Write Set (W=3): [ W ] [ W ] [ W ]
Read Set (R=3): [ R ] [ R ] [ R ]
▲
OVERLAP NODE
5. Mathematical Foundations & Probability Analysis
5.1 Quorum Tolerances
In a strict quorum cluster:
- Maximum tolerable dead nodes during writes:
- Maximum tolerable dead nodes during reads:
For typical production configurations:
- : Tolerates 1 dead node for both reads and writes.
- : Tolerates 2 dead nodes for both reads and writes.
5.2 The Weak Quorum Anomaly ()
If an engineering team configures to maximize write speed: The probability of a stale read after asynchronous updates is directly proportional to network partition duration, yielding eventual consistency with zero linearizability guarantees.
6. Production TypeScript Simulation: Leaderless Quorum with Read Repair
Below is an executable simulation of a Dynamo-style leaderless storage coordinator implementing Strict Quorums (), Vector Versioning, and Client-Side Read Repair:
export interface DataRecord {
key: string;
value: string;
version: number;
}
export class ReplicaNode {
public store = new Map<string, DataRecord>();
public isDown = false;
constructor(public readonly id: string = '') {}
public write(record: DataRecord): boolean {
if (this.isDown) return false;
const current = this.store.get(record.key);
if (!current || record.version >= current.version) {
this.store.set(record.key, { ...record });
return true;
}
return false;
}
public read(key: string): { reachable: boolean; record: DataRecord | undefined } {
if (this.isDown) {
return { reachable: false, record: undefined };
}
return { reachable: true, record: this.store.get(key) };
}
}
export class LeaderlessQuorumCoordinator {
constructor(
public readonly replicas: ReplicaNode[],
public readonly N: number, // Replication Factor (e.g. 3)
public readonly W: number, // Write Quorum (e.g. 2)
public readonly R: number // Read Quorum (e.g. 2)
) {
if (W + R <= N) {
console.warn(`WARNING: W (${W}) + R (${R}) <= N (${N}). Strict quorum not guaranteed!`);
}
}
public async put(key: string, value: string, version: number): Promise<boolean> {
const record: DataRecord = { key, value, version };
let acks = 0;
for (const node of this.replicas) {
if (node.write(record)) {
acks++;
}
}
if (acks >= this.W) {
return true;
}
throw new Error(`WriteTimeout: Only ${acks}/${this.W} required ACKs received`);
}
public async get(key: string): Promise<{ value: string | null; repairedNodes: number }> {
const responses: { node: ReplicaNode; record: DataRecord | undefined }[] = [];
for (const node of this.replicas) {
const res = node.read(key);
if (res.reachable) {
responses.push({ node, record: res.record });
}
}
if (responses.length < this.R) {
throw new Error(`ReadTimeout: Only ${responses.length}/${this.R} replicas responded`);
}
// Find record with highest version across reachable responders
let latest: DataRecord | null = null;
for (const resp of responses) {
if (resp.record) {
if (!latest || resp.record.version > latest.version) {
latest = resp.record;
}
}
}
// Execute Synchronous Client-Driven Read Repair for missing or stale replicas
let repairedNodes = 0;
if (latest) {
for (const resp of responses) {
if (!resp.record || resp.record.version < latest.version) {
resp.node.write(latest);
repairedNodes++;
}
}
}
return {
value: latest ? latest.value : null,
repairedNodes,
};
}
}
async function runDemo(): Promise<void> {
console.log('=== Replication Strategies: Quorum Read/Write ===');
const cluster = [new ReplicaNode('r1'), new ReplicaNode('r2'), new ReplicaNode('r3')];
const coordinator = new LeaderlessQuorumCoordinator(cluster, 3, 2, 2);
const writeRes = await coordinator.put('doc:1', 'Revision 1', 1);
console.log('Quorum write: success=' + writeRes);
const readRes = await coordinator.get('doc:1');
console.log('Quorum read: value=' + readRes.value + ', repairedNodes=' + readRes.repairedNodes);
}
runDemo();
7. Open-Source Implementation Analysis
A. PostgreSQL: Physical Streaming Replication
- WAL Sender & WAL Receiver: The primary server spawns a
walsenderprocess for each replica. As transactions commit, WAL records are streamed across TCP sockets to the replica’swalreceiverprocess. - Synchronous Commit Levels: Configured via
synchronous_commit:off: Transaction commits locally before writing WAL to disk.local: Commits after local WAL flush.remote_write: Commits after replica has received WAL into OS memory.on: Commits after replica has flushed WAL to disk (RPO = 0).
B. Apache Cassandra: Anti-Entropy via Merkle Trees
In Cassandra, background repair runs without impacting read/write paths:
- Replicas generate Merkle Trees (binary cryptographic hash trees) for specific token ranges.
- Nodes compare Merkle tree root hashes.
- If root hashes match, replicas are guaranteed identical. If hashes diverge, nodes traverse down the tree branches, pinpointing the exact out-of-sync token ranges in time and exchanging only missing SSTable blocks.
8. Failure Modes & Operational War Stories
War Story: The Split-Brain Double-Billing Disaster
- Incident: A financial payments platform ran PostgreSQL with an automatic failover script. A temporary 45-second network switch partition isolated the primary master in Datacenter A from Datacenter B.
- Failure: Datacenter B’s failover script assumed Master A had died, promoted Replica B to new Master, and redirected payment webhooks to B. Meanwhile, Master A was still alive and continued accepting payments from Asian payment gateways.
- Outcome: Both databases accepted independent writes with conflicting transaction sequences for 45 minutes. Automatic reconciliation was impossible; engineers spent 3 days manually stitching database WAL logs by hand.
- Mitigation: Deployed fencing tokens via consensus (Raft/etcd) with STONITH (“Shoot The Other Node In The Head”) hardware power-switch fencing: an isolated node must prove it holds an active etcd lease, or it forcibly terminates its own OS process.
9. Operational Decision Guide
| System Requirement | Recommended Architecture | Trade-Off Rationale |
|---|---|---|
| ACID Banking Ledger | Single-Leader with Sync Standby (RPO = 0) | Strict serializability; zero tolerance for write conflicts. |
| Global Retail Cart / Wiki Docs | Multi-Leader (CRDT conflict resolution) | Low write latency across continents; handles offline edits. |
| High-Throughput IoT / Metrics | Leaderless Cassandra () | Continuous write availability; linear scale; no leader bottleneck. |
10. Hands-on Operational Drills
- Simulate Stale Replica Repair: Use the TypeScript simulation with . Disable Node 3. Write Key
"user:101"with Version 1. Write Key"user:101"with Version 2. Re-enable Node 3. Execute a read query and verify that Node 3 is immediately repaired with Version 2 via Read Repair. - Verify Quorum Overlap: Change configuration to (). Demonstrate that a client can write Version 2 to Nodes 1 and 2, but subsequently read from Nodes 3 and 4, observing an invalid stale read.
View Reference Architecture & Worked Solutions (CORE-M18-001)
1. Worked Solution — Drill 1: Simulate Stale Replica Repair ()
- State Trace During Node 3 Outage:
- Set
cluster[2].isDown = true(r3offline). - Execute
await coordinator.put("user:101", "Alice-v1", 1)followed byawait coordinator.put("user:101", "Alice-v2", 2). Both writes succeed becauser1andr2are healthy (). At this point:r1.store.get("user:101"){ version: 2, value: "Alice-v2" }r2.store.get("user:101"){ version: 2, value: "Alice-v2" }r3.store.get("user:101")undefined(missed both writes while down)
- Set
- Recovery & Synchronous Read Repair:
- Set
cluster[2].isDown = false(r3returns online with an empty state for"user:101"). - Execute
const res = await coordinator.get("user:101"). All 3 replicas respond with{ reachable: true }, satisfying . - The coordinator identifies
latest = { key: "user:101", value: "Alice-v2", version: 2 }fromr1/r2, detects thatr3hasresp.record === undefined(or< latest.version), and callsr3.write(latest)before returning{ value: "Alice-v2", repairedNodes: 1 }. Subsequent reads fromr3alone now return Version 2.
- Set
2. Worked Solution — Drill 2: Verify Quorum Overlap Failure ()
- Pigeonhole Proof of Non-Overlap:
- With , the quorum sum is . Because , the write set and read set can be completely disjoint!
- Concrete Execution Trace Producing a Stale Read:
- Suppose all 5 nodes (
r1..r5) initially hold"user:101"atversion: 1("v1"). - A client writes
version: 2("v2"), which reachesr1andr2first (satisfying ACKs) while network delay or temporary packet drop preventsr3,r4, andr5from receiving"v2". - Immediately afterward, a second client issues a read with that queries the fastest responders
r3andr4. Both returnversion: 1("v1"). - Because neither
r1norr2was included in the read quorum of size , the coordinator seeslatest.version = 1across all responses and returns the stale value"v1"—violating Read-After-Write consistency. Raising () forces at least overlapping node (r1orr2) in every 3-node read sample.
- Suppose all 5 nodes (
11. Design Alternatives & Replication Trade-Off Matrix
| Replication Architecture | Write Availability | Read Staleness | Conflict Resolution | Best Architectural Fit |
|---|---|---|---|---|
| Single-Leader (MySQL / PG) | Low on leader crash | Zero on leader; variable on async replicas | None (Single serialization point) | Standard transactional RDBMS, enterprise ERP systems |
| Multi-Leader (Active-Active) | High (Writes proceed locally in any region) | Bounded by cross-region replication lag | CRDTs, Last-Write-Wins, or custom merge | Globally distributed SaaS with collaborative editing / user profiles |
| Leaderless Quorums (Cassandra / Dynamo) | Tunable via W and R (High with sloppy quorums) | Tunable (Strict quorum vs eventual) | Read repair, anti-entropy Merkle trees | High-throughput write ingestion, time-series, telemetry storage |
| Chain Replication | High read throughput | Strictly linearizable | None (Deterministic head-to-tail pipeline) | High-throughput storage systems requiring linearizability (Ceph) |
12. Authoritative References & Version Context
- Giuseppe DeCandia et al. (2007), “Dynamo: Amazon’s Highly Available Key-value Store”, ACM SOSP ‘07 (Sloppy Quorums and Hinted Handoff) — doi.org/10.1145/1294261.1294281
- Werner Vogels (2009), “Eventually Consistent”, Communications of the ACM 52(1), pp. 40–44 — doi.org/10.1145/1435417.1435432
- Robbert van Renesse and Fred B. Schneider (2004), “Chain Replication for Supporting High Throughput and Availability”, USENIX OSDI ‘04 — usenix.org
- Version Context: Quorum protocols reflect Apache Cassandra 4.x/5.0, Amazon DynamoDB, and ScyllaDB 5.x tunable consistency levels (ONE, QUORUM, LOCAL_QUORUM, ALL).