The Cheat Sheet¶
The Problem All Three Solve¶
Multiple nodes. Data must be consistent across them. Nodes can fail. Networks can fail. How do you stay correct?
Your Postgres + LSN Solution¶
Topology One primary. N replicas. You manage everything in app code.
Write path Always primary. CP. Fails if primary unreachable. Never splits.
Read path
- Critical → LSN check in Redis → primary if replica behind → CP
- Non critical → replica directly → AP
During partition
- Primary side → works normally
- Replica side → serves stale data silently
- Defence → primary heartbeat + proxy health checks
Consistency granularity Per user, per request. Most granular of all three.
Who manages consistency You. In application code. Visible, debuggable, domain aware.
What you give up Automatic failover. Primary is a single point of failure. Silently stale reads during partition if replica is isolated.
When it's the right choice Single region. One primary is acceptable. You want maximum control. Your scale doesn't demand more.
Quorum (Cassandra Style)¶
Topology Leaderless. Any node accepts writes. Any node serves reads.
Write path Coordinator writes to W nodes, waits for W acks. Client gets success. Rest get it async.
Read path Coordinator reads from R nodes, returns newest value. W + R > N → guaranteed overlap → CP. W + R ≤ N → AP.
During partition
- W + R > N → partition makes writes fail if can't reach W nodes → CP
- W + R ≤ N → writes succeed on reachable nodes → AP → conflicts on heal → LWW resolution → silent data loss possible
Consistency granularity Per query type. ONE, TWO, QUORUM, LOCAL_QUORUM, ALL. Not per user session.
Who manages consistency The database. You declare consistency level. DB coordinates internally.
What you give up Per user session consistency. Write ordering guarantee. Conflict free operation.
When it's the right choice High write throughput. Multi region with LOCAL_QUORUM. Leaderless needed. Availability more important than strict ordering.
Raft¶
Topology Always one elected leader. Rest are followers. All writes go to leader.
Write path Leader receives write → sends to all followers → waits for majority ack → commits → responds to client. Sync to majority. One slow follower doesn't block.
Read path
- Linearizable → from leader, leader confirms still leader via heartbeat → CP
- Follower read → follower checks commit index → same as your LSN check
- Stale read → any follower, no check → AP opt in
During partition
- Majority side → elects leader if needed, accepts writes, CP
- Minority side → refuses writes, no leader possible → unavailable
- On heal → followers catch up from leader log → clean, no conflicts
Consistency granularity Per operation. Linearizable or stale read. Write path has no choice — always CP.
Who manages consistency The protocol itself. No app code. No proxy. No LSN logic. Mathematical guarantee.
What you give up Leaderless writes. Leader is still a write bottleneck unless sharded (CockroachDB style).
When it's the right choice Strong consistency required. Automatic failover needed. Write ordering is critical. Foundation for etcd, CockroachDB, Kubernetes.
Setup for the example¶
N = 5 nodes: A (leader), B, C, D, E (followers). Current term = 4. A client sends a write: SET x = 5.
Step 1: Client sends the write to the leader¶
The client's request lands on A, the current leader. (If the client mistakenly sends it to a follower like C, C just rejects it and redirects the client to A — followers never accept writes directly.)
Step 2: Leader appends to its own local log — uncommitted¶
A appends the entry to its log:
Log on A: [..., (term=4, index=57, cmd="SET x=5")]
At this point the entry exists only on A and is not yet committed. It hasn't been applied to A's actual state machine (the key-value store) yet, and the client hasn't gotten a response yet. It's just sitting in the log.
Step 3: Leader fans out AppendEntries RPCs¶
A sends an AppendEntries RPC to B, C, D, E in parallel. Each RPC contains:
- The new entry (term=4, index=57, cmd)
- The term and index of the entry immediately before it, so followers can verify their log is in sync (this is Raft's consistency check — if a follower's log doesn't match up to that point, it rejects the entry and the leader backs up and retries with earlier entries until they realign)
Step 4: Followers append and acknowledge¶
Each follower that successfully appends the entry to its own local log replies with success. Say B and D reply quickly; C is slow; E is partitioned and never responds.
A: has it (leader itself counts)
B: acked
D: acked
C: pending
E: unreachable
Step 5: Leader checks for majority — this is the commit point¶
A counts acks: itself + B + D = 3 out of 5. That's a majority (floor(5/2)+1 = 3). The moment that threshold is hit, A marks index 57 as committed.
This is the exact same quorum math as W = QUORUM from your earlier questions — the write is "durable" the instant a majority holds it, regardless of the remaining minority.
Step 6: Leader applies to its state machine and responds to client¶
Only now does A apply SET x=5 to its actual key-value store, and only now does it send the success response back to the client. Everything before this point was invisible to the client — they were just waiting.
Step 7: Followers learn the commit point and catch up¶
On the next heartbeat (or next AppendEntries), A includes its current commitIndex (57). Any follower that sees a commitIndex higher than what it's applied locally applies those entries to its own state machine too. So B and D apply it soon after. C, once it stops being slow, gets the entry and the commit index and catches up. E, once the partition heals, gets a burst of AppendEntries covering everything it missed, including index 57, and catches up too.
What matters for your CAP/quorum framing¶
- W (write quorum) = majority, always, in Raft — it's not configurable per-call like Cassandra. This is a hardcoded design choice, not a tunable knob.
- The client only ever sees a response after commit (majority ack) — never before. So Raft-backed systems are inherently CP-flavored on writes: a partitioned minority simply can't accept writes, because it can never reach majority alone.
- E being unreachable didn't block anything — this is the whole point of quorum-based commit: the leader doesn't wait for all N, just a majority, so a slow or dead minority doesn't stall the system.
Leader Crashes¶
Let's continue the same scenario. A is leader, term = 4, index 57 just committed (A, B, D have it; C is slow; E was partitioned). Now A crashes.
Step 1: Followers stop hearing heartbeats¶
A was sending periodic heartbeats to B, C, D, E to say "I'm alive, stay followers." Once A crashes, those heartbeats stop. Each follower has its own randomized election timeout (typically 150–300ms range, randomized per node so they don't all fire at once).
Step 2: First follower to time out becomes a candidate¶
Say B's timeout fires first. B:
- Increments its term: 4 → 5
- Transitions from follower → candidate
- Votes for itself (1 vote)
- Sends
RequestVoteRPCs to C, D, E, including: new term (5), and B's last log index/term (57, term=4)
Step 3: Other nodes decide whether to vote¶
Each recipient checks two things before granting a vote:
Term check — is the candidate's term ≥ mine? B says term 5, everyone else is still on term 4 → yes, they update their own term to 5 and consider voting.
Log up-to-date check — this is the safety-critical part. A voter only grants its vote if the candidate's log is at least as up-to-date as its own (compared by last entry's term, then index). This is exactly the mechanism that protects the committed entry:
- D has index 57 (committed) — same as B. B is at least as up-to-date. D votes yes.
- C was slow and might be missing index 57, or might have caught up right before the crash. If C's log is behind B's, C votes yes anyway (B is ahead, that's fine — the rule only blocks voting for candidates behind you). If C's log is somehow ahead — impossible here since B is the most caught-up follower along with D — that'd block it, but that's not the case in this scenario.
- E was partitioned and is missing a bunch of entries, including 57. E's log is behind B's → E votes yes too, since being behind the candidate is fine; the block only fires the other direction.
So B collects votes from itself + D + C + E = potentially all of them, but it only needs a majority (3 of 5) to win.
Step 4: B wins, becomes leader for term 5¶
Once B has 3+ votes, it transitions to leader. It immediately starts sending heartbeats (AppendEntries with no new entries) to assert authority and stop anyone else from starting an election.
Step 5: Why index 57 survives — the overlap guarantee in action¶
This is the part that directly answers "what if the leader goes down — do we lose the write?" Recall: index 57 was committed because it reached a majority {A, B, D}. B needed a majority {itself + 2 others} to win the election. Two majorities out of 5 nodes must share at least one node — pigeonhole, same as before. In this run, that overlapping node was B itself, since B was one of the three that had committed 57. Because of the log up-to-date check in Step 3, no node whose log is missing 57 could have beaten B to leadership if that node's log is behind — a node missing 57 is definitionally behind a node that has it, so it cannot win against B or any other candidate who has it. The committed entry cannot be lost by a leader change.
Step 6: The new leader reconciles any stragglers¶
Now leader B sends AppendEntries to C and E. If either is missing entries (like E, who missed a bunch during the partition), B's AppendEntries consistency check (comparing preceding index/term) fails against their logs, so B backs up and resends earlier entries until they resync — eventually E gets everything it missed, including 57, and catches up fully.
Step 7: What about A?¶
When A eventually comes back online, it's still thinks it's leader of term 4. It gets an AppendEntries or heartbeat from B carrying term 5. Since 5 > 4, A immediately recognizes it's stale, steps down to follower, updates its term to 5, and starts accepting B's leadership. Any uncommitted entries A had that never reached majority (there weren't any in this example, but if there had been, say index 58 that only existed on A) are simply discarded — they were never committed, so losing them violates no guarantee.
The one-line summary¶
Leader crash → randomized timeouts break the tie on who campaigns first → election requires majority votes → the log up-to-date check ensures only a candidate holding all committed entries can win → majority-overlap math guarantees such a candidate exists → new leader takes over, resyncs stragglers, old leader steps down cleanly when it resurfaces.¶
Side By Side¶
| Your Postgres + LSN | Quorum | Raft | |
|---|---|---|---|
| Leader | Yes, manual | None | Yes, elected automatically |
| Write conflicts possible | No | Yes (AP mode) | No |
| Write ordering guaranteed | Yes | No | Yes |
| Failover | Manual via Patroni | Automatic | Automatic, mathematical |
| Read granularity | Per user session | Per query type | Per operation |
| Consistency managed by | Your code | Database | Protocol |
| Multi region | Painful | LOCAL_QUORUM | Cross region latency |
| ACID | Full (single node) | No | Yes (CockroachDB/Spanner) |
| CAP default | CP writes, AP reads | AP (ONE default) | CP |
| Conflicts on partition heal | None | LWW, silent loss possible | None |
| Real world | Your current stack | Cassandra, DynamoDB | etcd, CockroachDB, TiDB |
The One Paragraph¶
Your Postgres setup is the most granular and controllable — you make CP vs AP decisions per user per request in code you own. Quorum removes the leader, distributes writes across nodes, gives you a consistency dial per query type, but loses write ordering and risks silent data loss under AP mode. Raft brings the leader back but makes it automatic and mathematically guaranteed — strongest consistency, cleanest failure recovery, but leader is still a write bottleneck unless you shard. All three are answering the same physics problem — light speed exists, networks fail, you cannot have perfect consistency and perfect availability simultaneously. They just disagree on where to take the pain.