Chapter 12: Database Replication

Database replication is the practice of keeping copies of the same data on multiple machines connected via a network. It's fundamental to building scalable, highly available systems. This chapter explores single-leader, multi-leader, and leaderless replication strategies, their trade-offs, failure modes, and conflict resolution techniques.

Key Topics: Master-slave replication, replication lag, multi-leader conflicts, quorum-based systems, failover strategies, CDC patterns, real-world implementations.

12.1 Why Replicate?

Replication serves multiple critical purposes in distributed systems:

Purpose Benefit Use Case Trade-off
High Availability System continues if one node fails 99.99% uptime SLA, zero-downtime deployments Complexity in failover, potential for split-brain
Read Scaling Distribute read load across replicas Read-heavy workloads (90% reads, 10% writes) Eventual consistency, replication lag
Geographic Locality Data closer to users, lower latency Global applications (EU users → EU replica) Cross-region network costs, conflict resolution
Disaster Recovery Full data copy survives datacenter failure Compliance requirements, business continuity Recovery time objective (RTO), recovery point objective (RPO)
Offline Operation Local replica enables offline work Mobile apps, distributed collaboration (CRDTs) Complex conflict resolution, eventual consistency

Replication vs. Backup

Aspect Replication Backup
Purpose Availability, performance, locality Point-in-time recovery, historical data
Update Speed Near real-time (seconds to milliseconds) Periodic (hourly, daily, weekly)
Data Corruption Replicates corruption immediately Previous backups remain uncorrupted
Recovery Time Seconds (automatic failover) Minutes to hours (restore process)
Use Case Node failure, read scaling, low latency Human error (DELETE FROM users), compliance, auditing
Critical Distinction: Replication is NOT a substitute for backups. A corrupted write (DELETE FROM orders WHERE 1=1) replicates to all nodes instantly. You need backups to restore to a pre-corruption state.

12.2 Single-Leader Replication (Master-Slave)

The most common replication architecture. One node is the leader (master, primary); all writes go to it. Other nodes are followers (slaves, replicas, secondaries); they receive updates from the leader.

graph TB subgraph "Single-Leader Replication" Client1[Client Write] Client2[Client Read] Client3[Client Read] Leader[(Leader
Write + Read)] Follower1[(Follower 1
Read Only)] Follower2[(Follower 2
Read Only)] Follower3[(Follower 3
Read Only)] Client1 -->|1. Write| Leader Leader -.->|2. Replication Log| Follower1 Leader -.->|2. Replication Log| Follower2 Leader -.->|2. Replication Log| Follower3 Client2 -->|3. Read| Follower1 Client3 -->|3. Read| Follower2 end style Leader fill:#4a9eff style Follower1 fill:#6c757d style Follower2 fill:#6c757d style Follower3 fill:#6c757d

12.2.1 Synchronous vs. Asynchronous Replication

Synchronous Replication

sequenceDiagram participant Client participant Leader participant Follower1 participant Follower2 Client->>Leader: Write(x=5) activate Leader Leader->>Follower1: Replicate(x=5) activate Follower1 Follower1-->>Leader: ACK deactivate Follower1 Note over Leader: Leader waits for
synchronous follower ACK Leader->>Follower2: Replicate(x=5) Note over Follower2: Async follower
(fire-and-forget) Leader-->>Client: Write OK deactivate Leader Note over Client,Follower2: Follower1 is guaranteed to have x=5
before client gets response

Pros:

Cons:

Asynchronous Replication

sequenceDiagram participant Client participant Leader participant Follower1 participant Follower2 Client->>Leader: Write(x=5) activate Leader Leader-->>Client: Write OK deactivate Leader Note over Leader: Leader responds immediately
without waiting for followers Leader->>Follower1: Replicate(x=5) Leader->>Follower2: Replicate(x=5) Note over Follower1,Follower2: Followers update eventually
(could be milliseconds to seconds later)

Pros:

Cons:

Semi-Synchronous (Hybrid)

Practical compromise: one follower is synchronous, others are asynchronous. If the synchronous follower becomes unavailable, another async follower is promoted to synchronous.

Strategy Durability Write Latency Availability Real-World Use
Fully Synchronous Highest (zero data loss) High (wait for all) Low (any follower down = writes fail) Rare (financial transactions, critical systems)
Semi-Synchronous High (one follower guaranteed) Medium (wait for one) High (one follower can fail) MySQL semi-sync, PostgreSQL sync_standby_names
Asynchronous Medium (potential data loss) Low (no waiting) Highest (all followers can fail) Most systems by default (PostgreSQL, MySQL, MongoDB)
Interview Tip: Always clarify durability requirements. "Is it acceptable to lose the last few seconds of writes if the leader crashes?" shapes the entire replication strategy.

12.2.2 Replication Lag Problems

Asynchronous replication introduces eventual consistency: replicas will become consistent eventually, but there's a window where they're stale. This causes three classic problems:

Problem 1: Reading Your Own Writes

sequenceDiagram participant User participant Leader participant Follower User->>Leader: POST /profile (update bio) Leader-->>User: 200 OK Note over Leader: Bio updated to
"Software Engineer" User->>Follower: GET /profile Note over Follower: Replication lag:
still has old bio
"Student" Follower-->>User: Old bio: "Student" Note over User: User confused:
just updated bio,
but sees old value

Solution: Read-After-Write Consistency

Problem 2: Monotonic Reads Violation

sequenceDiagram participant User participant Replica1 participant Replica2 participant Leader Leader->>Replica1: Replicate comment #101 Note over Replica1: Lag: 1 second behind Leader->>Replica2: Replicate comment #101 Note over Replica2: Lag: 5 seconds behind User->>Replica1: GET /comments Replica1-->>User: Comments: #99, #100, #101 Note over User: Sees comment #101 User->>Replica2: GET /comments (refresh) Replica2-->>User: Comments: #99, #100 Note over User: Comment #101 disappeared!
(User confused: "moving backwards in time")

Solution: Monotonic Reads

Problem 3: Consistent Prefix Reads Violation

sequenceDiagram participant Observer participant LeaderShard1 participant LeaderShard2 participant FollowerShard1 participant FollowerShard2 Note over LeaderShard1,LeaderShard2: Partitioned database (users sharded) LeaderShard1->>FollowerShard1: Replicate: "Alice: How are you?" LeaderShard2->>FollowerShard2: Replicate: "Bob: I'm good!" Note over FollowerShard2: Faster replication
(less loaded) Observer->>FollowerShard2: Read Bob's messages FollowerShard2-->>Observer: "Bob: I'm good!" Observer->>FollowerShard1: Read Alice's messages Note over FollowerShard1: Slower replication
(lagging behind) FollowerShard1-->>Observer: (empty) Note over Observer: Sees reply before question!
"Bob: I'm good!" with no context

Solution: Consistent Prefix Reads

Problem Symptom Example Solutions
Reading Your Own Writes User doesn't see their own updates Update profile bio, refresh, see old bio Read own data from leader; monotonic read tokens; sticky sessions
Monotonic Reads Time appears to go backward See comment #101, refresh, it's gone Sticky sessions; session affinity; timestamp tracking
Consistent Prefix Reads Causally ordered writes appear out of order See reply before question in chat Colocate causal data; version vectors; read from leader
Real Incident: Facebook 2012 - Users reported "messages from the future" where replies appeared before original messages due to cross-datacenter replication lag and partitioned data.

12.3 Replication Log Implementation

How does the leader transmit changes to followers? Four main approaches:

12.3.1 Statement-Based Replication

Leader logs every write statement (INSERT, UPDATE, DELETE) and sends it to followers.

-- Leader executes and logs:
UPDATE accounts SET balance = balance + 100 WHERE id = 42;

-- Followers replay the same statement

Problems:

Workarounds:

12.3.2 Write-Ahead Log (WAL) Shipping

Database writes all changes to an append-only WAL before applying them. Leader sends raw WAL to followers.

-- WAL entry (low-level format):
Page 5, Offset 100: 0x4A3F... (binary data representing row)

Pros:

Cons:

12.3.3 Logical (Row-Based) Replication

Leader logs changes at row level in a storage-engine-independent format.

-- Logical log entry (human-readable representation):
INSERT INTO users (id, name, email) VALUES (123, 'Alice', 'alice@ex.com');
UPDATE users SET email = 'alice2@ex.com' WHERE id = 123;
DELETE FROM users WHERE id = 456;

Pros:

Cons:

Examples: MySQL binlog (row-based mode), PostgreSQL logical decoding, MongoDB oplog

12.3.4 Trigger-Based Replication

Use database triggers to capture changes and write them to a separate table (replication log). Custom application reads log and replicates.

-- PostgreSQL example:
CREATE TRIGGER replicate_users
AFTER INSERT OR UPDATE OR DELETE ON users
FOR EACH ROW EXECUTE FUNCTION log_change();

Pros:

Cons:

Replication Method Granularity Storage Coupling Version Skew Performance Use Cases
Statement-Based SQL statements Low Tolerant Compact logs Simple schemas, deterministic queries (MySQL old default)
WAL Shipping Disk page bytes Very High Intolerant Very fast PostgreSQL streaming replication, hot standby
Logical (Row-Based) Row changes Low Tolerant Fast MySQL binlog, PostgreSQL logical replication, CDC
Trigger-Based Application-defined None Very Tolerant Slower Selective replication, custom logic, Databus (LinkedIn)

12.4 Multi-Leader Replication (Master-Master)

Multiple leaders can accept writes. Each leader replicates changes to all other leaders and their followers.

graph TB subgraph "Multi-Leader: Multi-Datacenter" subgraph DC1["Datacenter 1 (US East)"] L1[(Leader 1)] F1[(Follower 1A)] F2[(Follower 1B)] L1 -.-> F1 L1 -.-> F2 end subgraph DC2["Datacenter 2 (EU West)"] L2[(Leader 2)] F3[(Follower 2A)] F4[(Follower 2B)] L2 -.-> F3 L2 -.-> F4 end subgraph DC3["Datacenter 3 (Asia)"] L3[(Leader 3)] F5[(Follower 3A)] F6[(Follower 3B)] L3 -.-> F5 L3 -.-> F6 end L1 <-->|Cross-DC Replication| L2 L2 <-->|Cross-DC Replication| L3 L1 <-->|Cross-DC Replication| L3 Client1[Client US] --> L1 Client2[Client EU] --> L2 Client3[Client Asia] --> L3 end style L1 fill:#4a9eff style L2 fill:#4a9eff style L3 fill:#4a9eff

12.4.1 Use Cases

Multi-Datacenter Operation

Aspect Single-Leader Multi-Leader
Write Latency Cross-datacenter (high if remote leader) Local datacenter (low)
Datacenter Failure Failover to new leader (downtime) Each datacenter continues independently
Network Tolerance Inter-DC link required for writes Tolerates inter-DC network partition
Conflicts None (single source of truth) Frequent (concurrent writes to different leaders)

Offline Clients (CRDTs, Mobile Apps)

Each device (laptop, phone) acts as a leader. When online, syncs with other "leaders" (other devices, server).

graph LR subgraph "Offline-Enabled App (Calendar, Notes)" Laptop[(Laptop
Local DB
Leader)] Phone[(Phone
Local DB
Leader)] Server[(Server
Leader)] Laptop -->|Sync when online| Server Phone -->|Sync when online| Server Server -->|Push updates| Laptop Server -->|Push updates| Phone end Note1[User edits offline
on laptop] Note2[User edits offline
on phone] Note1 -.-> Laptop Note2 -.-> Phone style Laptop fill:#4a9eff style Phone fill:#4a9eff style Server fill:#4a9eff

Examples: CouchDB, PouchDB, Google Docs (Operational Transformation), Figma, Git

Collaborative Editing

Google Docs, Figma: each user's browser is a "leader" making local edits, broadcasting to other users.

12.4.2 Conflict Detection and Resolution

Multi-leader's biggest challenge: write conflicts.

sequenceDiagram participant User1 participant LeaderUS participant LeaderEU participant User2 Note over LeaderUS,LeaderEU: Document title initially: "Draft" User1->>LeaderUS: UPDATE doc SET title='Final v1' User2->>LeaderEU: UPDATE doc SET title='Final v2' Note over LeaderUS: Title = 'Final v1' Note over LeaderEU: Title = 'Final v2' LeaderUS->>LeaderEU: Replicate: title='Final v1' LeaderEU->>LeaderUS: Replicate: title='Final v2' Note over LeaderUS,LeaderEU: CONFLICT!
Which title wins?

Conflict Avoidance (Best Strategy)

Design application so conflicts can't happen:

Conflict Resolution Strategies

1. Last-Write-Wins (LWW)
// Each write gets a timestamp (or unique ID)
Write 1: { title: "Final v1", timestamp: 1620000001000 }
Write 2: { title: "Final v2", timestamp: 1620000002000 }

// Keep write with highest timestamp
Result: "Final v2" wins

Pros: Simple, deterministic, eventual convergence

Cons: Data loss - discards one write entirely (users lose their changes)

2. Merge / Application-Level Resolution
// Conflict detected:
Version A: { tags: ['urgent', 'bug'] }
Version B: { tags: ['urgent', 'security'] }

// Application-level merge: union of tags
Result: { tags: ['urgent', 'bug', 'security'] }

Pros: No data loss, domain-specific logic

Cons: Requires custom code per data type

3. Prompt User to Resolve

Git-style: show both versions, ask user to pick.

Title:
<<<<<<< Leader US
Final v1
=======
Final v2
>>>>>>> Leader EU

Pros: Correct resolution (human decides)

Cons: Poor UX (interrupts workflow), not automatic

4. Keep All Versions (Siblings)

Store both conflicting values; application reads all siblings and decides.

// Riak, CouchDB approach:
GET /document/123
{
  "siblings": [
    { "title": "Final v1", "version": "abc123" },
    { "title": "Final v2", "version": "def456" }
  ]
}

Pros: No data loss, application has full context

Cons: Complexity pushed to application (every read must handle siblings)

5. CRDTs (Conflict-Free Replicated Data Types)

Data structures designed to merge automatically and deterministically.

CRDT Type Data Structure Merge Rule Use Case
G-Counter Grow-only counter Sum of all increments Like counts, view counts (increment-only)
PN-Counter Positive-Negative counter Sum(increments) - Sum(decrements) Shopping cart quantities, votes
G-Set Grow-only set Union of all adds Tags, labels (add-only)
2P-Set Two-Phase Set Adds - Removes (can't re-add) User permissions (once revoked, stays revoked)
OR-Set Observed-Remove Set Track add/remove with unique IDs Shopping cart items, collaborators
LWW-Element-Set Last-Write-Wins Set Timestamp per element Presence (online/offline status)

Example: OR-Set (Add/Remove without conflicts)

// Replica 1: Add "apple" with unique ID
ORSet.add("apple", id=uuid1)

// Replica 2: Concurrently remove "apple" (different ID)
ORSet.remove("apple", id=uuid2)

// Merge: Add wins (uuid1 still in set, uuid2 not present to remove)

12.4.3 Replication Topologies

graph TB subgraph Circular["Circular Topology"] C1[(Leader 1)] --> C2[(Leader 2)] C2 --> C3[(Leader 3)] C3 --> C1 end subgraph Star["Star Topology"] S2[(Leader 2)] --> S1[(Leader 1
Central)] S3[(Leader 3)] --> S1 S4[(Leader 4)] --> S1 S1 --> S2 S1 --> S3 S1 --> S4 end subgraph AllToAll["All-to-All Topology"] A1[(Leader 1)] A2[(Leader 2)] A3[(Leader 3)] A4[(Leader 4)] A1 <--> A2 A1 <--> A3 A1 <--> A4 A2 <--> A3 A2 <--> A4 A3 <--> A4 end
Topology Latency Fault Tolerance Infinite Loop Risk Use Case
Circular Medium (multi-hop) Low (break in circle = partition) High (need loop detection) Legacy systems, 3-5 nodes
Star Low (single hop) Low (central node = SPOF) Low (central node prevents loops) MySQL Group Replication (hub-and-spoke)
All-to-All Low (direct replication) High (any node can fail) High (need loop detection) Modern multi-leader (Cassandra, CockroachDB)

Infinite Loop Prevention

Tag each write with unique ID and originating leader. Replicas ignore writes they've already seen.

// Write originates at Leader 1
Write { id: uuid1, leader: 1, data: "value" }

// Leader 2 receives it, tags with leader=1, replicates to Leader 3
// Leader 3 receives it, sees leader=1, replicates to Leader 1
// Leader 1 receives it, sees own ID (leader=1), IGNORES (no loop)
Real Incident: MySQL 5.5 circular replication - forgotten loop detection caused write to replicate infinitely, amplifying to millions of redundant log entries and crashing all replicas.

12.4.4 Multi-Leader Trade-offs

Aspect Pros Cons
Performance Low write latency (local leader), high availability Conflict resolution overhead, complex reads
Availability Survives datacenter failures, network partitions Split-brain risk (conflicting writes)
Consistency Eventual consistency (converges) No strong consistency guarantees, conflict complexity
Operational Complexity Fault-tolerant by design Very high: conflict bugs hard to detect, replication loops, version skew
Best Practice: Avoid multi-leader unless you have a strong use case (multi-datacenter, offline operation). Single-leader is simpler and sufficient for 95% of applications.

12.5 Leaderless Replication

No designated leader. Clients write to multiple replicas in parallel. Popularized by Amazon Dynamo (2007), used by Cassandra, Riak, Voldemort.

graph TB Client[Client] R1[(Replica 1)] R2[(Replica 2)] R3[(Replica 3)] R4[(Replica 4)] R5[(Replica 5)] Client -->|Write: x=5| R1 Client -->|Write: x=5| R2 Client -->|Write: x=5| R3 Client -.->|Write: x=5| R4 Client -.->|Write: x=5| R5 R1 -->|ACK| Client R2 -->|ACK| Client R3 -->|ACK| Client R5 -.->|Timeout| Client Note1["Write succeeds when W=3 replicas ACK Quorum: W=3, N=5"] Note1 -.-> Client style R1 fill:#28a745 style R2 fill:#28a745 style R3 fill:#28a745 style R4 fill:#ffc107 style R5 fill:#dc3545

12.5.1 Quorum Reads and Writes

Key parameters:

Quorum Condition: W + R > N

Guarantees: if W + R > N, at least one replica in the read quorum has the latest value.

sequenceDiagram participant Client participant R1 participant R2 participant R3 participant R4 participant R5 Note over Client,R5: N=5, W=3, R=3 (W+R=6 > N=5) Client->>R1: Write(x=10, version=v5) Client->>R2: Write(x=10, version=v5) Client->>R3: Write(x=10, version=v5) Client->>R4: Write(x=10, version=v5) Client->>R5: Write(x=10, version=v5) R1-->>Client: ACK R2-->>Client: ACK R3-->>Client: ACK Note over Client: W=3 ACKs received
Write succeeds R4--XClient: Timeout R5--XClient: Timeout Note over Client: Later: Read request Client->>R1: Read(x) Client->>R2: Read(x) Client->>R3: Read(x) R1-->>Client: x=10, v5 R2-->>Client: x=10, v5 R3-->>Client: x=8, v4 (stale) Note over Client: R=3 responses received
Return highest version: x=10, v5

Common Quorum Configurations

Configuration N W R Durability Read Latency Write Latency Use Case
Strong Consistency 3 2 2 High Medium Medium Default (W+R=4 > N=3)
Read-Heavy 3 3 1 Very High Low High Eventual consistency OK, fast reads
Write-Heavy 3 1 3 Low High Low Fast writes, eventual consistency
High Availability 5 2 2 Medium Medium Medium Tolerate 2 node failures (N - W = 3)
Eventually Consistent 3 1 1 Low Low Low W+R=2 ≤ N=3 (no quorum guarantee)
Critical: W + R ≤ N means no quorum guarantee. Reads may return stale data even if write succeeded. Use only for eventually consistent workloads (e.g., shopping cart, view counts).

12.5.2 Handling Stale Replicas

Read Repair

Client reads from R replicas, detects version mismatch, writes latest version back to stale replicas.

// Client reads R=3 replicas:
R1: x=10, version=v5
R2: x=10, version=v5
R3: x=8,  version=v4  (stale!)

// Client detects R3 is stale, sends repair write:
Client → R3: Write(x=10, version=v5)

Pros: Low overhead (only repairs on read), works for frequently read data

Cons: Rarely read data stays stale indefinitely

Anti-Entropy Process

Background process continuously compares replicas and repairs differences.

// Anti-entropy daemon compares Merkle trees:
Replica 1 Merkle Root: 0xABCD1234
Replica 2 Merkle Root: 0xABCD1234
Replica 3 Merkle Root: 0xDEADBEEF  (mismatch!)

// Narrow down to specific keys and repair

Merkle Trees: Hash tree of data. Comparing roots quickly identifies mismatched ranges.

graph TB Root[Root Hash:
0xABCD1234] L1[Hash A-M:
0x1111AAAA] L2[Hash N-Z:
0x2222BBBB] L11[Hash A-F:
0x3333CCCC] L12[Hash G-M:
0x4444DDDD] L21[Hash N-S:
0x5555EEEE] L22[Hash T-Z:
0x6666FFFF] Root --> L1 Root --> L2 L1 --> L11 L1 --> L12 L2 --> L21 L2 --> L22 L11 --> Keys1[Keys: apple, banana] L12 --> Keys2[Keys: grape, mango] L21 --> Keys3[Keys: orange, peach] L22 --> Keys4[Keys: tomato, zucchini] style Root fill:#4a9eff

Pros: Repairs rarely-read data, comprehensive

Cons: High overhead (continuously comparing), repair lag (runs periodically)

12.5.3 Sloppy Quorums and Hinted Handoff

Problem: Network partition or replica failure → can't reach W replicas → write fails.

Sloppy Quorum: Allow writes to succeed by including nodes outside the "home" replica set.

graph TB subgraph "Normal Quorum (N=3, W=2)" C1[Client] N1[(Replica 1
Home)] N2[(Replica 2
Home)] N3[(Replica 3
Home - DOWN)] C1 --> N1 C1 --> N2 C1 -.X.-> N3 Note1[Write fails:
Only 2/3 replicas
reachable, need W=2] end subgraph "Sloppy Quorum (W=2, Hinted Handoff)" C2[Client] H1[(Replica 1
Home)] H2[(Replica 2
Home)] H3[(Replica 3
Home - DOWN)] H4[(Replica 4
Temporary)] C2 --> H1 C2 --> H2 C2 --> H4 C2 -.X.-> H3 Note2[Write succeeds:
2 home + 1 temporary
Hint: belongs to R3] H4 -.->|When R3 recovers:
Hinted Handoff| H3 end style N3 fill:#dc3545 style H3 fill:#dc3545 style H4 fill:#ffc107

Hinted Handoff: Temporary replica (R4) stores a "hint" that data belongs to R3. When R3 recovers, R4 hands off the data.

Pros: Increased write availability (writes succeed during failures)

Cons: No durability guarantee (W replicas ACKed, but not the "home" replicas; if temp replica fails, data lost)

Quorum Type Availability Durability Consistency Use Case
Strict Quorum Medium (requires W home replicas) High (W home replicas durable) Strong (W+R > N) Default for most leaderless systems
Sloppy Quorum High (accepts temporary replicas) Medium (hinted handoff may fail) Eventual (no guarantee until handoff) High-availability mode (Cassandra, Riak)
Cassandra Example: consistency_level=ONE (W=1, R=1) with sloppy quorums enables 99.999% write availability but no consistency guarantees.

12.5.4 Detecting Concurrent Writes

Two clients write to same key concurrently (no coordination). How to detect conflicts?

Version Vectors (Dotted Version Vectors)

Each replica tracks version number per client/replica. Captures causal relationships.

// Initial state: x=0, version={}

// Client A writes x=1:
Replica 1: x=1, version={A:1}
Replica 2: x=1, version={A:1}

// Client B writes x=2 (concurrent, hasn't seen A's write):
Replica 3: x=2, version={B:1}

// Replica 1 receives B's write:
// Versions {A:1} and {B:1} are concurrent (neither dominates)
// Store both as siblings: [{x=1, {A:1}}, {x=2, {B:1}}]

// Client C reads, sees siblings, merges, writes x=3:
Replica 1: x=3, version={A:1, B:1, C:1}

Domination: Version {A:2, B:1} dominates {A:1, B:1} (A advanced, B same or advanced).

Concurrent: {A:1, B:0} and {A:0, B:1} neither dominates (different clients advanced).

12.5.5 Leaderless Trade-offs

Aspect Pros Cons
Availability Very high (no SPOF, sloppy quorums) No linearizability (even with quorums)
Performance Low latency (parallel reads/writes) Read/write amplification (query R/W replicas)
Fault Tolerance Tolerates N-W failures for writes, N-R for reads Quorum failures (network partition splits quorum)
Consistency Tunable (adjust W, R) Complex conflict resolution (siblings, version vectors)
Real Incident: Amazon S3 2008 - Sloppy quorum + network partition caused temporary replica to accept write, then fail before hinted handoff, resulting in data loss for a small percentage of writes.

12.6 Replication Lag & Solutions (Revisited)

Replication lag is the time delta between leader's write and follower's update. In asynchronous replication, lag can range from milliseconds to minutes.

graph LR subgraph "Replication Lag Timeline" T0[T0: Leader writes
balance=100] T1[T1: Follower 1 replicates
lag=10ms] T2[T2: Follower 2 replicates
lag=500ms] T3[T3: Follower 3 replicates
lag=5000ms] T0 --> T1 T1 --> T2 T2 --> T3 end Note1[User queries Follower 3
at T2: sees stale balance] Note1 -.-> T2

12.6.1 Read-After-Write Consistency

Guarantee: user sees their own writes immediately (no lag for own data).

Implementation Strategies

Strategy Mechanism Pros Cons
Read from Leader Route user's own-data reads to leader Simple, zero lag Higher leader load, doesn't scale reads
Timestamp Tracking Leader returns timestamp with write; client includes in next read; route to follower caught up to that timestamp Scales reads to followers Complexity (track timestamps per user)
Session Affinity Route user to same replica for session duration Monotonic reads guaranteed Poor load balancing, doesn't help cross-device
Logical Timestamps (Causal) Write returns logical clock; read waits for replica to catch up to that clock Precise, works cross-device High complexity (Lamport clocks, version vectors)
sequenceDiagram participant User participant Leader participant Follower participant LoadBalancer User->>Leader: POST /profile (update) Leader-->>User: 200 OK, timestamp=1234567890 Note over User: User stores timestamp=1234567890
in session cookie User->>LoadBalancer: GET /profile
(cookie: timestamp=1234567890) LoadBalancer->>Follower: Check: caught up to 1234567890? alt Follower caught up Follower-->>LoadBalancer: Yes (current=1234567891) LoadBalancer->>Follower: GET /profile Follower-->>User: Latest profile else Follower lagging Follower-->>LoadBalancer: No (current=1234567800) LoadBalancer->>Leader: GET /profile (fallback) Leader-->>User: Latest profile end

12.6.2 Monotonic Reads

Guarantee: user never sees data "go backward" (if read X, subsequent reads see X or newer).

sequenceDiagram participant User participant LoadBalancer participant Follower1 participant Follower2 Note over Follower1: Lag: 1 second behind Note over Follower2: Lag: 10 seconds behind User->>LoadBalancer: GET /posts (request 1) LoadBalancer->>Follower1: Route to Follower1 (hash(userID) % N) Follower1-->>User: Posts: [1, 2, 3, 4, 5] Note over User: Sees post #5 User->>LoadBalancer: GET /posts (request 2, refresh) LoadBalancer->>Follower1: Route to Follower1 (same hash) Follower1-->>User: Posts: [1, 2, 3, 4, 5, 6] Note over User: Monotonic: sees post #5 again + new #6
Never routes to Follower2 (more stale)

Implementation: Hash user ID to consistent replica (session affinity).

12.6.3 Consistent Prefix Reads

Guarantee: if writes happened in order A → B → C, reads see them in that order (not B, A, C).

Problem: Partitioned/sharded databases replicate independently; shard 1 may lag differently than shard 2.

sequenceDiagram participant User participant Shard1Leader participant Shard2Leader participant Shard1Follower participant Shard2Follower Note over Shard1Leader,Shard2Leader: Partitioned by user ID Shard1Leader->>Shard1Follower: Replicate: "Alice: Hi!" (timestamp=100) Shard2Leader->>Shard2Follower: Replicate: "Bob: Hi back!" (timestamp=101) Note over Shard2Follower: Replicates faster
(low load) Note over Shard1Follower: Replicates slower
(high load, lag) User->>Shard2Follower: Read Bob's messages Shard2Follower-->>User: "Bob: Hi back!" (timestamp=101) User->>Shard1Follower: Read Alice's messages Note over Shard1Follower: Still lagging, hasn't received
Alice's message yet Shard1Follower-->>User: (empty) Note over User: VIOLATION: Sees Bob's reply
before Alice's question!

Solutions:

12.7 Failover

Failover is the process of promoting a follower to leader when the current leader fails.

stateDiagram-v2 [*] --> Normal: System running Normal --> Suspected: Health check timeout Suspected --> Confirmed: Multiple nodes agree leader is down Confirmed --> ElectNewLeader: Trigger failover ElectNewLeader --> ReconfigureClients: New leader elected ReconfigureClients --> Normal: Clients use new leader Suspected --> Normal: False alarm (network blip) ElectNewLeader --> SplitBrain: Old leader recovers before reconfiguration SplitBrain --> Fenced: Fencing token / epoch number Fenced --> Normal: Old leader steps down

12.7.1 Failover Process

Step 1: Detect Leader Failure

No foolproof way to detect failure (network partition vs. crashed node look identical).

Detection Method Mechanism False Positive Risk Detection Time
Heartbeat Timeout Leader sends periodic heartbeat; if N consecutive timeouts, declare dead High (network blip) Fast (seconds)
Consensus (Raft, Paxos) Majority of nodes vote that leader is down Low (majority agreement) Medium (seconds to tens of seconds)
External Monitor (Zookeeper) External service monitors leader; declares failure Medium (monitor itself could fail) Medium (depends on monitor latency)

Timeout Trade-off:

Step 2: Elect New Leader

Election Method Algorithm Pros Cons
Manual Promotion Human operator promotes follower Safe (human judgment) Slow (human latency), downtime
Pre-Designated Follower Follower with highest priority automatically promoted Fast, deterministic Single successor = SPOF if also down
Consensus-Based (Raft) Followers vote; majority elects leader Fault-tolerant, no SPOF Slower (voting round), requires majority
Quorum-Based (Paxos) Proposers compete; highest epoch number wins Highly fault-tolerant Complex to implement

Step 3: Reconfigure Clients and Replicas

All clients and followers must learn about the new leader.

sequenceDiagram participant OldLeader participant Follower1 participant Follower2 participant Client participant ConfigService Note over OldLeader: Leader fails (crashed) Follower1->>Follower2: Vote: I'm the new leader Follower2-->>Follower1: ACK (vote granted) Note over Follower1: Follower1 elected as leader Follower1->>ConfigService: Update: new_leader=Follower1 Client->>ConfigService: Query: who is leader? ConfigService-->>Client: Leader = Follower1 Client->>Follower1: Write(x=10) Follower1-->>Client: OK Note over OldLeader: Old leader recovers OldLeader->>ConfigService: Announce: I'm the leader ConfigService-->>OldLeader: Rejected: Leader = Follower1 (higher epoch) OldLeader->>Follower1: Demote self, become follower

Configuration Service: Centralized registry (ZooKeeper, etcd, Consul) storing current leader. Clients query it to discover leader.

12.7.2 Failure Modes and Problems

Problem 1: Split-Brain

Old leader recovers, believes it's still the leader. Now two leaders accept writes → data divergence.

graph TB subgraph "Split-Brain Scenario" C1[Client A] C2[Client B] OldLeader[(Old Leader
Thinks it's leader)] NewLeader[(New Leader
Thinks it's leader)] C1 --> OldLeader C2 --> NewLeader OldLeader -->|Write: x=10| D1[Database State A] NewLeader -->|Write: x=20| D2[Database State B] Note1[DISASTER: Two conflicting
writes, data divergence] end style OldLeader fill:#dc3545 style NewLeader fill:#dc3545

Solution: Fencing Tokens

Each leader gets a monotonically increasing epoch number (or generation ID). Storage rejects writes from leaders with stale epoch numbers.

// New leader elected with epoch=5
NewLeader: Write(x=10, epoch=5)
Storage: Accept (epoch=5 is highest seen)

// Old leader recovers, still has epoch=4
OldLeader: Write(x=20, epoch=4)
Storage: Reject (epoch=4 < current epoch=5)

// Old leader queries config service, learns it's demoted
OldLeader: Step down, become follower
Real Incident: GitHub 2012 - Split-brain during MySQL failover caused old leader to accept writes for 15 minutes while new leader also accepted writes. Result: data divergence, 7 hours to reconcile manually.

Problem 2: Data Loss During Failover

If old leader had asynchronous followers, recent writes may not have replicated. New leader is missing those writes.

// Leader receives write:
Leader: Write(x=100) at time T0

// Leader crashes at T0 + 1ms (before replicating to followers)

// Follower promoted to leader:
NewLeader: Latest value: x=50 (old value)

// x=100 is lost forever

Solutions:

Problem 3: False Positive Failover

Leader is actually fine, but network partition prevents followers from reaching it. Followers elect new leader → now two leaders (split-brain).

Solution: Quorum-Based Failure Detection

Require majority of nodes to agree leader is down before triggering failover.

// 5-node cluster: Leader, Follower1, Follower2, Follower3, Follower4

// Network partition: Followers 1 & 2 can't reach Leader
Follower1: Leader timeout!
Follower2: Leader timeout!

// Followers 3 & 4 can still reach Leader
Follower3: Leader is fine
Follower4: Leader is fine

// Vote: 2/5 say down, 2/5 say up
// No majority → NO FAILOVER (avoid false positive)

12.7.3 Automatic vs. Manual Failover

Failover Type RTO (Recovery Time) Risk Use Case
Automatic Seconds to minutes (fast) High (split-brain, false positives, data loss) Systems requiring 99.99%+ uptime, where downtime is unacceptable
Manual Minutes to hours (slow) Low (human verifies before promoting) Systems where correctness > availability, low-traffic (acceptable downtime)
Hybrid (Auto with approval) Minutes (fast once approved) Medium (human confirms, but time pressure) Critical systems with on-call staff
Best Practice: Start with manual failover. Graduate to automatic only after extensive testing and monitoring. Have kill switch to disable auto-failover if bugs detected.

12.8 Conflict Resolution Deep Dive

12.8.1 Operational Transformation (OT)

Used by Google Docs, Etherpad. Transform concurrent operations so they can be applied in any order and converge to same result.

Example: Collaborative Text Editing

// Initial document: "Hello"

// User A: Insert "!" at position 5
Operation A: Insert("!", 5)  →  "Hello!"

// User B: (concurrent) Delete character at position 0
Operation B: Delete(0)  →  "ello"

// Problem: Applying in different orders gives different results:
// A then B: "Hello!" → "ello!"  (delete "H")
// B then A: "ello" → "ello!"     (delete "H", insert "!")
// SAME RESULT ✓

// But what if B's operation is at position 5?
// User A: Insert("!", 5)  →  "Hello!"
// User B: Insert("?", 5)  →  "Hello?"

// Applying A then B:
// "Hello" → "Hello!" → "Hello?!"  (B's position transformed to 6)

// Applying B then A:
// "Hello" → "Hello?" → "Hello?!"  (A's position transformed to 6)

// Transform function:
// Transform(Insert("!", 5), Insert("?", 5)):
//   If concurrent inserts at same position, use tie-breaker (user ID)
//   User A < User B → A's insert goes first → B's position becomes 6

OT Challenges:

12.8.2 CRDTs (Conflict-Free Replicated Data Types)

Data structures designed to merge automatically without conflicts. Two variants:

State-Based CRDTs (Convergent)

Replicas send full state; merge function is commutative, associative, idempotent.

// G-Counter (Grow-only counter)
type GCounter = map[replicaID]int

// Increment on replica 1:
replica1 = {1: 5, 2: 3, 3: 2}  (replica 1 incremented 5 times)

// Increment on replica 2:
replica2 = {1: 5, 2: 4, 3: 2}  (replica 2 incremented once)

// Merge: element-wise max
merge(replica1, replica2) = {1: max(5,5), 2: max(3,4), 3: max(2,2)}
                          = {1: 5, 2: 4, 3: 2}

// Total count: sum(5 + 4 + 2) = 11

Operation-Based CRDTs (Commutative)

Replicas send operations; operations are commutative (order doesn't matter).

// OR-Set (Observed-Remove Set)
type ORSet = {
  adds: set of (element, uniqueID)
  removes: set of uniqueID
}

// Replica 1: Add "apple"
adds: {("apple", uuid1)}
removes: {}

// Replica 2: (concurrent) Remove "apple"
// But Replica 2 doesn't know about uuid1 (hasn't synced yet)
// Can't remove what it hasn't observed → remove is no-op

// After sync, Replica 2 sees ("apple", uuid1), can now remove it
removes: {uuid1}

// Merge: "apple" is in set if any add UUID not in removes
Result: "apple" is in set (uuid1 in adds, not in removes)

CRDT Examples

CRDT Operations Merge Rule Limitations Use Cases
G-Counter Increment only Sum per-replica counts Can't decrement Like counts, view counts, page hits
PN-Counter Increment, decrement Sum(increments) - Sum(decrements) Counter only (not arbitrary values) Shopping cart quantity, votes
G-Set Add only Union of all adds Can't remove Add-only tags, immutable labels
2P-Set Add, remove (once) Adds - Removes Can't re-add after remove Revoked permissions, blacklist
OR-Set Add, remove (multiple) Track unique IDs per add Unbounded growth (store IDs forever) Shopping cart, collaborators, watchlist
LWW-Register Set value Keep value with highest timestamp Data loss (discards earlier writes) User status, presence (online/offline)
RGA (Replicated Growable Array) Insert, delete Position based on causal tree Complex (tombstones for deletes) Text editing, collaborative lists
Interview Tip: CRDTs are mathematically correct but not a silver bullet. They require specific data structures (can't CRDT-ify arbitrary data) and have overhead (metadata, tombstones).

12.8.3 Application-Level Conflict Resolution

Custom logic in application code to resolve conflicts.

Example: Shopping Cart Merge

// User adds item on phone (offline):
CartPhone: ["Laptop", "Mouse"]

// User adds item on laptop (offline):
CartLaptop: ["Laptop", "Keyboard"]

// Sync: Detect conflict (both modified cart)

// Resolution: Union merge (keep all items)
CartMerged: ["Laptop", "Mouse", "Keyboard"]

// With quantities (more complex):
CartPhone: [{"Laptop": 1}, {"Mouse": 2}]
CartLaptop: [{"Laptop": 1}, {"Keyboard": 1}]

// Resolution: Sum quantities for same item
CartMerged: [{"Laptop": 2}, {"Mouse": 2}, {"Keyboard": 1}]

Example: Calendar Event Merge

// User edits event on phone: title="Meeting with Alice", time=2pm
// User edits event on laptop: title="Meeting", location="Room 101"

// Conflict: Both edited same event

// Resolution: Field-level merge (keep latest per field)
// Title: "Meeting with Alice" (phone edited after laptop)
// Time: 2pm (phone edited)
// Location: "Room 101" (laptop edited)

// Result: {title: "Meeting with Alice", time: 2pm, location: "Room 101"}

12.8.4 Conflict-Free by Design

Architect system to avoid conflicts entirely.

Technique Approach Example
Immutable Data Never update; only insert new versions Event sourcing: append-only event log (no conflicts on append)
Commutative Operations Design operations to commute (order doesn't matter) Increment counter (x+1, x+2 in any order = x+3)
Single Writer per Record Partition data so each record has one owner User's profile editable only by that user
Conflict-Free Schema Schema designed for mergeability Tags as set (union merge), not single value

12.9 Change Data Capture (CDC)

Change Data Capture: capture database changes (inserts, updates, deletes) as a stream of events. Enables derived data systems (caches, search indexes, analytics) to stay in sync.

graph LR subgraph "Change Data Capture Architecture" DB[(Primary Database
PostgreSQL)] WAL[Write-Ahead Log] CDC[CDC Tool
Debezium] Kafka[Kafka Topic:
db.changes] DB --> WAL WAL --> CDC CDC --> Kafka Cache[Redis Cache] Search[Elasticsearch] Analytics[Data Warehouse] Kafka --> Cache Kafka --> Search Kafka --> Analytics App[Application] --> DB App -.->|Reads| Cache end style DB fill:#4a9eff style Kafka fill:#ffc107

12.9.1 CDC Approaches

Log-Based CDC (Best Practice)

Parse database's replication log (WAL, binlog, oplog).

-- PostgreSQL WAL entry:
BEGIN;
INSERT INTO users (id, name, email) VALUES (123, 'Alice', 'alice@ex.com');
COMMIT;

-- Debezium captures and emits to Kafka:
{
  "before": null,
  "after": {"id": 123, "name": "Alice", "email": "alice@ex.com"},
  "op": "c",  // create
  "ts_ms": 1620000000000
}

Pros:

Cons:

Trigger-Based CDC

Database triggers write changes to a separate "changelog" table.

-- Trigger on users table:
CREATE TRIGGER cdc_users
AFTER INSERT OR UPDATE OR DELETE ON users
FOR EACH ROW EXECUTE FUNCTION log_change();

-- Changelog table:
CREATE TABLE changelog (
  id SERIAL PRIMARY KEY,
  table_name TEXT,
  operation TEXT,  -- INSERT, UPDATE, DELETE
  old_data JSONB,
  new_data JSONB,
  timestamp TIMESTAMP
);

Pros:

Cons:

Application-Level CDC

Application writes to DB and emits change event.

// Application code:
function createUser(user) {
  db.insert('users', user);
  kafka.emit('user.created', user);  // Dual write
}

Pros:

Cons:

12.9.2 Outbox Pattern

Solve dual-write problem: write to DB and "outbox" table in same transaction. CDC reads outbox and emits to Kafka.

sequenceDiagram participant App participant DB participant OutboxTable participant CDC participant Kafka App->>DB: BEGIN TRANSACTION App->>DB: INSERT INTO users (...) App->>OutboxTable: INSERT INTO outbox (event='user.created', payload=...) App->>DB: COMMIT Note over DB,OutboxTable: Both writes in same transaction
(atomic: both succeed or both fail) CDC->>OutboxTable: Poll for new events OutboxTable-->>CDC: event='user.created', payload=... CDC->>Kafka: Emit event Kafka-->>CDC: ACK CDC->>OutboxTable: DELETE processed event
-- Outbox table:
CREATE TABLE outbox (
  id SERIAL PRIMARY KEY,
  aggregate_type TEXT,  -- 'user', 'order', etc.
  aggregate_id TEXT,
  event_type TEXT,      -- 'created', 'updated', 'deleted'
  payload JSONB,
  created_at TIMESTAMP
);

-- Application writes:
BEGIN;
INSERT INTO users (id, name) VALUES (123, 'Alice');
INSERT INTO outbox (aggregate_type, aggregate_id, event_type, payload)
VALUES ('user', '123', 'created', '{"name": "Alice"}');
COMMIT;

-- CDC process polls outbox and emits to Kafka

Pros:

Cons:

12.9.3 CDC Tools

Tool Databases Approach Output Use Case
Debezium MySQL, PostgreSQL, MongoDB, SQL Server, Oracle Log-based Kafka Industry standard, production-ready, open-source
Maxwell MySQL Binlog parsing Kafka, Kinesis, stdout Lightweight, MySQL-specific
AWS DMS Most databases Log-based / trigger-based S3, Kinesis, Redshift Managed service, AWS-native
Kafka Connect Any (with connector) Plugin-based Kafka Ecosystem (100+ connectors)
Databus (LinkedIn) Oracle, MySQL Log-based Custom Legacy (LinkedIn internal), inspired Kafka Connect

12.9.4 CDC Use Cases

Use Case Pattern Benefit
Cache Invalidation DB change → CDC → invalidate cache key Always consistent cache (no stale reads)
Search Index Sync DB change → CDC → update Elasticsearch Real-time search without polling DB
Data Warehouse ETL DB change → CDC → load into Redshift/Snowflake Near-real-time analytics (no batch ETL)
Microservices Event Bus Service A DB change → CDC → Service B listens Decouple services (event-driven architecture)
Audit Log DB change → CDC → append-only audit log Compliance, forensics (who changed what when)
Materialized Views DB change → CDC → recompute derived views Maintain precomputed aggregates in real-time

12.10 Replication in Real Systems

12.10.1 PostgreSQL Streaming Replication

Single-leader, asynchronous (or synchronous) replication using WAL shipping.

-- Primary server (leader):
wal_level = replica
max_wal_senders = 5

-- Standby server (follower):
primary_conninfo = 'host=primary-db port=5432'
hot_standby = on  (allow reads on standby)

-- Synchronous replication (wait for one standby):
synchronous_standby_names = 'standby1'
synchronous_commit = on
Feature Details
Replication Method WAL shipping (physical replication)
Synchronous Option Yes (synchronous_standby_names)
Read Replicas Yes (hot standby mode)
Automatic Failover No (requires external tool: patroni, repmgr)
Version Skew Tolerance No (WAL format tied to PostgreSQL version)

12.10.2 MySQL Group Replication

Multi-leader replication with consensus-based conflict resolution (Paxos-based).

-- Group Replication plugin:
INSTALL PLUGIN group_replication SONAME 'group_replication.so';

-- Configure group:
SET GLOBAL group_replication_bootstrap_group=ON;
START GROUP_REPLICATION;

-- Multi-primary mode: all nodes accept writes
group_replication_single_primary_mode = OFF
Feature Details
Replication Method Row-based (binlog) + Paxos consensus
Multi-Leader Yes (multi-primary mode), or single-primary
Conflict Resolution First committer wins (abort concurrent transaction)
Automatic Failover Yes (consensus-based leader election)
Limitations Max 9 nodes, high latency in multi-primary (consensus overhead)

12.10.3 MongoDB Replica Sets

Single-leader replication with automatic failover (Raft-like election).

// Initialize replica set:
rs.initiate({
  _id: "myReplicaSet",
  members: [
    { _id: 0, host: "mongo1:27017" },
    { _id: 1, host: "mongo2:27017" },
    { _id: 2, host: "mongo3:27017" }
  ]
})

// Read preference (route reads to secondaries):
db.collection.find().readPref("secondary")
Feature Details
Replication Method Oplog (operation log, logical replication)
Leader Election Automatic (Raft-like, majority vote)
Read Replicas Yes (secondaries), with read preference
Replication Lag Handling Read concern: "majority" (wait for majority to replicate)
Failover Time ~10 seconds (election + reconfiguration)

12.10.4 Cassandra (Leaderless)

Leaderless replication with tunable consistency (quorum-based).

-- Write with consistency level QUORUM (W = (N+1)/2):
INSERT INTO users (id, name) VALUES (123, 'Alice');
  USING CONSISTENCY QUORUM;

-- Read with consistency level QUORUM (R = (N+1)/2):
SELECT * FROM users WHERE id = 123;
  USING CONSISTENCY QUORUM;

-- Eventual consistency (fast, no guarantee):
USING CONSISTENCY ONE;
Feature Details
Replication Method Leaderless (peer-to-peer)
Consistency Levels ONE, QUORUM, ALL, LOCAL_QUORUM, EACH_QUORUM
Conflict Resolution Last-write-wins (timestamp-based)
Repair Mechanisms Read repair, anti-entropy (nodetool repair)
Partitioning Consistent hashing (virtual nodes)

12.10.5 CockroachDB (Raft-per-Range)

Distributed SQL with Raft consensus per data range (shard). Strong consistency by default.

-- Raft replication automatically managed:
CREATE TABLE users (id INT PRIMARY KEY, name TEXT);

-- Each range (64MB by default) is a Raft group:
-- Range 1: keys 0-1000 → Raft group (nodes 1, 2, 3)
-- Range 2: keys 1001-2000 → Raft group (nodes 2, 3, 4)

-- Reads/writes go through Raft leader for that range
INSERT INTO users (id, name) VALUES (123, 'Alice');
-- Leader for range containing id=123 replicates via Raft
Feature Details
Replication Method Raft consensus (per range)
Consistency Serializable (strongest ACID guarantee)
Automatic Failover Yes (Raft leader election per range)
Rebalancing Automatic (moves ranges to balance load)
Trade-off Higher write latency (consensus on every write)

12.10.6 Comparison Table

System Architecture Consistency Failover Use Case
PostgreSQL Single-leader Strong (sync replication), eventual (async) Manual or tool-based OLTP, traditional relational apps
MySQL Single-leader (default), multi-leader (Group Replication) Eventual (async), strong (semi-sync) Manual or Group Replication auto OLTP, web apps
MongoDB Single-leader (replica set) Eventual, tunable (read/write concern) Automatic (Raft-like) Document store, real-time apps
Cassandra Leaderless Tunable (ONE, QUORUM, ALL) N/A (no leader) High write throughput, time-series, IoT
CockroachDB Multi-leader (Raft per range) Serializable (strong) Automatic (Raft) Distributed SQL, strong consistency required
DynamoDB Leaderless (dynamo-style) Eventual (default), strong (optional) N/A (no leader) Serverless, high availability, AWS-native

12.11 Summary

Key Takeaways

Replication Strategies

  • Single-Leader: Simplest, most common. Synchronous = strong consistency but high latency; asynchronous = low latency but replication lag. Use for 95% of apps.
  • Multi-Leader: Low write latency, survives datacenter failures. Complex conflict resolution. Use for multi-datacenter or offline operation.
  • Leaderless: Highest availability, tunable consistency. No SPOF. Use for high-write, eventual consistency workloads (IoT, time-series).

Replication Lag Problems

  • Reading Your Own Writes: Route user's own data to leader or use monotonic read tokens.
  • Monotonic Reads: Use sticky sessions (hash user ID to consistent replica).
  • Consistent Prefix Reads: Colocate causal data in same partition; use version vectors.

Failover

  • Automatic failover: fast but risky (split-brain, false positives, data loss). Use fencing tokens (epoch numbers) to prevent split-brain.
  • Manual failover: slow but safe. Start here; graduate to automatic only after extensive testing.

Conflict Resolution

  • Avoidance: Best strategy. Route user to same leader; partition data; single writer per record.
  • Last-Write-Wins: Simple but causes data loss. Acceptable for ephemeral data (user status).
  • CRDTs: Automatic merging, no conflicts. Limited to specific data structures (counters, sets, registers).
  • Application-Level: Custom logic per data type (e.g., union merge for tags). Flexible but complex.

Change Data Capture

  • Capture DB changes as event stream. Enables real-time derived systems (cache, search, analytics).
  • Log-based CDC (Debezium) = best practice. Outbox pattern solves dual-write problem.

Real-World Systems

  • PostgreSQL: WAL shipping, single-leader, strong consistency option. Use for traditional OLTP.
  • MongoDB: Oplog replication, automatic failover, tunable consistency. Use for document store.
  • Cassandra: Leaderless, tunable quorums, eventual consistency. Use for high-write, time-series.
  • CockroachDB: Raft per range, serializable. Use for distributed SQL with strong consistency.
Interview Checklist:
  • Can you explain the difference between synchronous and asynchronous replication? Trade-offs?
  • What are the three replication lag problems? How would you solve each?
  • How does failover work? What is split-brain, and how do you prevent it?
  • Describe quorum-based replication (leaderless). What is W+R > N? Sloppy quorums?
  • How do CRDTs enable conflict-free merging? Give an example (G-Counter, OR-Set).
  • What is Change Data Capture? How does it differ from application-level event emission?
  • Compare PostgreSQL vs. Cassandra replication: when would you use each?