The problem: 2PC blocks when the coordinator dies#
TL;DRthe 30-second version
- 2PC blocks: after a participant votes yes it is 'in-doubt' β it has promised to commit but doesn't know the decision. If the coordinator crashes right then, the participant can't commit (maybe someone voted no) and can't abort (maybe the coordinator already logged commit). It waits, holding locks. See Two-Phase Commit (/twopc).
- 3PC's idea: add a pre-commit phase between the vote and the commit. The coordinator only says 'commit' after everyone has first acknowledged 'pre-commit' β so before anyone commits, everyone already knows the decision is commit.
- That extra buffer state lets a stranded participant decide safely on a timeout: reached pre-commit β commit; never reached it β abort. No node is ever one step away from both outcomes at once, which is what made 2PC block.
- Under a fail-stop model (nodes only crash, the network stays connected) 3PC is non-blocking. That's the whole selling point.
- The catch: under a network partition, 3PC can split-brain β one side commits, the other aborts β and end up inconsistent. Plus it costs a third round trip. So real systems skip it and replicate the decision with consensus (Raft/Paxos, /raft/sim) or use Sagas (/saga/sim) instead.
Start with the wound 3PC is trying to close. In two-phase commit, a participant that has voted yes is now bound: it has durably promised to commit if told to, so it holds its locks and waits. That waiting node is 'in-doubt' β it knows what it promised, but not what the group decided. Normally the coordinator resolves the doubt one message later by sending commit or abort.
Now crash the coordinator in exactly that gap β after it has collected the votes but before it announces the outcome. The in-doubt participant is trapped. It cannot commit on its own, because maybe another participant voted no and the real decision is abort. It cannot abort on its own, because maybe every vote was yes and the coordinator already made the decision commit. Either guess risks disagreeing with a peer, so the safe move is to make no move: keep the locks, keep waiting. That is what 'blocking' means β the protocol stops making progress, and there is no timeout it can safely take to escape.
The fix: a pre-commit phase in the middle#
3PC keeps the same cast as 2PC β one coordinator, a set of participants β and the same goal of an all-or-nothing outcome. It just splits the single 'commit' step into two, turning two phases into three. The names are canCommit, preCommit, and doCommit.
Phase 1 β canCommit. The coordinator asks every participant 'can you commit?'. Each participant checks whether it is able to (constraints hold, it can acquire the locks) and votes yes or no. This is the voting phase, and it mirrors 2PC's prepare. If any vote is no, the coordinator sends abort to everyone and it's over.
Phase 2 β preCommit. This is the new phase, and the whole point of the protocol. If every vote was yes, the coordinator does not commit yet. Instead it sends preCommit to everyone. A participant that receives preCommit learns one crucial fact: everybody voted yes, so the decision is going to be commit. It records that it is now 'prepared to commit', acknowledges, and waits for the final go-ahead. Nothing is permanent yet β but everyone now knows which way this is heading.
Phase 3 β doCommit. Once the coordinator has collected an acknowledgement of preCommit from every participant, it sends doCommit. Each participant makes its change permanent, releases its locks, and acknowledges. The transaction is done.
- Coordinator β all: canCommit? Each participant votes YES (it can commit) or NO.
- Any NO β coordinator sends ABORT to everyone and stops.
- All YES β coordinator sends preCommit. Each participant records 'prepared to commit' β now knowing the decision is commit β and acks.
- All acks in β coordinator sends doCommit. Each participant commits, releases locks, and acks.
- The extra round (preCommit β ack) is the only structural difference from 2PC. Everything 3PC gains, it gains from that round.
PredictWhat does a participant know the instant it receives preCommit that it did NOT know right after voting yes?
Hint: Think about what has to be true for the coordinator to send preCommit at all.
It knows every participant voted yes, so the decision is definitely commit β it just hasn't been told to make it permanent yet. Right after voting yes, a participant knows only its own vote; the group decision could still be abort if some peer voted no. But the coordinator only sends preCommit after collecting a yes from everyone. So receiving preCommit converts 'I promised to commit if asked' into 'the answer is commit'. That single piece of extra knowledge is what lets a stranded participant decide safely on a timeout instead of blocking β which the next section unpacks.
Why the extra phase makes it non-blocking#
Now replay the coordinator crash that froze 2PC, but with the pre-commit phase in place. A participant loses contact with the coordinator and has to decide for itself what to do. In 3PC it can, because its own state tells it the answer.
- A participant that has received preCommit can safely commit on a timeout. Receiving preCommit means everyone voted yes, so the decision was always going to be commit. No peer can be aborting.
- A participant that voted yes but never received preCommit can safely abort on a timeout. If it hasn't seen preCommit, then no participant can have committed yet β because the coordinator only sends doCommit after everyone acks preCommit. Aborting can't contradict a commit that hasn't happened.
- In practice a survivor first runs a termination protocol β it asks the other reachable participants what state they are in, and the group agrees on commit if anyone reached preCommit, or abort otherwise, then elects a new coordinator to finish. The timeout rules above are the safe default each node falls back on.
Go deeperGo deeper: the state machine and Skeen's theorem
Dale Skeen formalized this in 1981. He modeled each participant as a state machine and proved a clean condition: a commit protocol is non-blocking if and only if no reachable state is adjacent to both a commit state and an abort state, and there is no state from which the protocol could go directly to either outcome depending on messages that a crash might hide. 2PC violates this β its 'voted yes, waiting' state is adjacent to both commit and abort. 3PC's extra phase adds an intermediate 'buffer' state (prepared-to-commit) so that the abort-reachable states and the commit-reachable states are separated by at least one step. That separation is exactly what lets an isolated node infer the outcome from its own state.
The recovery rule follows directly. On losing the coordinator, survivors run a termination protocol: gather everyone's state; if any reachable participant is in the prepared-to-commit (or committed) state, the group commits; if none is, the group aborts. A fresh coordinator is elected to drive the chosen outcome to completion. As long as failures are crash-only and every survivor can still talk to every other survivor, this always terminates with one agreed outcome β no one blocks.
The honest catch: network partitions break it#
Everything above assumes a fail-stop world: nodes crash outright, but the ones still alive can always reach each other. Real networks don't behave that way. A network partition can split the participants into two groups that are each alive and healthy but cannot talk across the divide. To a node on one side, a node on the other side is indistinguishable from a crashed node β same silence, same timeout. And that is where 3PC's safe-looking timeout turns unsafe.
The failure is a split-brain: the two sides each run the termination protocol among themselves, reach different conclusions that each look locally correct, and commit versus abort the same transaction. When the partition heals, the databases disagree. Atomicity β the one guarantee the whole protocol exists to provide β is gone.
Go deeperGo deeper: the split-brain, step by step
- All participants vote yes in phase 1. The coordinator starts phase 2 and sends preCommit β but a partition cuts the group in half mid-send.
- Group A did receive preCommit. Group B did not.
- The coordinator is now unreachable to at least one side. Both sides time out waiting for it and fall back to their termination protocols.
- Group A sees that its members reached prepared-to-commit, applies the 'anyone in preCommit means commit' rule, and commits.
- Group B sees that no member reached preCommit, applies the 'no one in preCommit means abort' rule, and aborts.
- Both decisions were locally safe by 3PC's own rules β yet the transaction is now committed on one side and aborted on the other. When the network heals, the state is inconsistent.
The root cause is that a timeout cannot distinguish 'the other side crashed' from 'the other side is alive but unreachable'. 3PC's non-blocking guarantee is built on treating a timeout as a crash. Under a partition that assumption is false, so each side confidently makes a decision that the other side contradicts. 2PC, by contrast, refuses to guess and blocks β it gives up liveness to keep safety. 3PC gives up safety to keep liveness, and a partition is exactly the case where that trade goes wrong.
Cost: the extra round trip you pay for#
The non-blocking property is not free. The whole difference from 2PC is a third round of messages, and that round lands on the critical path β the transaction can't finish until it completes, and the participants hold their locks across all three phases instead of two.
| Resource | 2PC | 3PC | Why the difference |
|---|---|---|---|
| Round trips | 2 | 3 | The added preCommit β ack round between vote and commit |
| Messages (n participants) | ~4n | ~6n | Three request/reply rounds instead of two β O(n) per phase |
| Latency | β 2 Γ slowest round trip | β 3 Γ slowest round trip | Each phase waits for the last reply; 3PC has one more phase to wait on |
| Lock hold time | Across 2 phases | Across 3 phases | Locks are held longer, so contended rows stall for longer |
| Blocking on crash | Yes (in-doubt window) | No (under fail-stop) | The buffer state lets survivors decide instead of waiting |
Trade-offs: when the extra phase is and isn't worth it
3PC's bargain is the mirror image of 2PC's. 2PC chooses safety over liveness: it would rather freeze than risk two participants disagreeing. 3PC chooses liveness over safety in the crash case: it keeps making progress, at the price of a correctness hole under partitions. Neither is a clean win, which is the honest reason the industry walked past both toward consensus.
- Pro: non-blocking under crash-only failures β a coordinator crash no longer freezes prepared participants holding locks.
- Pro: survivors can reach an outcome with a timeout-based termination protocol, so the system keeps moving.
- Con: not partition-tolerant β a network split can commit on one side and abort on the other, violating atomicity.
- Con: a third round trip on every transaction: more latency, more messages, longer lock hold times.
- Con: it still leans on a coordinator and still assumes bounded message delays; real datacenter networks break that assumption routinely.
2PC vs 3PC vs consensus commit vs Saga
| Protocol | Atomic? | Blocks on coordinator crash? | Round trips | Partition-safe? | Used in practice |
|---|---|---|---|---|---|
| 2PC | Yes | Yes β in-doubt participants freeze | 2 | Safe but unavailable (blocks) | XA/JTA, Postgres prepared txns, MSDTC |
| 3PC | Yes (no partitions) | No β survivors decide via timeout | 3 | No β can split-brain | Almost never (textbook) |
| Paxos/Raft Commit | Yes | No β majority survives node loss | 2 + consensus | Safe and available | Spanner, CockroachDB, modern systems |
| Saga | No β eventual via compensation | No | n local txns + compensations | Available | Long-lived microservice workflows |
The row that matters is Paxos/Raft Commit. It gets 3PC's non-blocking property AND partition safety, because it decides by majority quorum instead of by 'any reachable subgroup can decide'. Under a partition, at most one side has a majority, so at most one side can act β the other is forced to wait. 3PC removed the block but kept the door open to split-brain; consensus closes that door. This is why modern distributed databases replicate the commit decision with a consensus group rather than reaching for a third phase.
In the wild: mostly a lesson, not a deployment
3PC is one of the most-taught, least-deployed protocols in distributed systems. Its value today is didactic: it's the clean example of how you'd try to fix blocking, and of why that fix isn't enough once you admit that networks partition. What real systems actually run instead:
- Consensus-replicated commit is the standard modern answer. Google Spanner runs 2PC across shards, but each participant and the coordinator is itself a Paxos group, so the decision is majority-replicated and no single crash blocks it. CockroachDB and TiKV do the equivalent with Raft. This is 'Paxos Commit' (Gray & Lamport) in production β see the Raft simulator (/raft/sim) for the majority mechanic it relies on.
- Sagas sidestep the whole atomic-commit problem for long-lived, multi-service workflows: each step commits locally and a failure triggers compensating actions to undo earlier steps, trading atomicity for availability (/saga/sim).
- Classic 2PC standards (X/Open XA, JTA, Microsoft MSDTC, PostgreSQL's PREPARE TRANSACTION) are still widely used inside a trusted datacenter, blocking risk accepted β because the practical fix for the block is to make the coordinator's decision durable and recoverable, not to add a third phase.
- 3PC itself shows up mainly in coursework, textbooks, and the occasional research prototype. If you find it in a production system, it's rare enough to be noteworthy.
Common misconceptions & gotchas
Does 3PC actually solve 2PC's blocking problem?
Only under a fail-stop model, where nodes crash but the survivors can always reach each other. In that world, yes β the pre-commit phase lets a stranded participant decide from its own state instead of freezing. But real networks partition, and under a partition 3PC can split-brain (commit on one side, abort on the other). So it trades one problem (blocking) for a worse one (inconsistency) in the exact conditions that matter most.
Why is a partition worse than a crash for 3PC?
A crash removes a node from the group; the survivors still agree with each other. A partition keeps every node alive but splits them into groups that can't communicate β and each group can independently reach a different 'safe' decision. A timeout can't tell 'crashed' from 'unreachable', so both sides act confidently and contradict each other. 3PC's whole safety argument assumes a timeout means a crash, and a partition breaks that assumption.
If consensus is better, why learn 3PC at all?
Because it's the clearest illustration of a deep idea: a protocol is non-blocking only if no live node sits one step from both commit and abort. 3PC shows you how to engineer that separation with a buffer state β and then shows you exactly why it isn't sufficient without a quorum. Understanding that gap is what makes Paxos/Raft commit click: the missing ingredient is majority agreement, not more phases.
Is the pre-commit phase the same as 2PC's prepare?
No β that's a common mix-up. 2PC's 'prepare' is phase 1, the voting round (canCommit in 3PC). 3PC's preCommit is a NEW second phase that happens after voting: it doesn't ask a question, it announces 'everyone voted yes, the decision is commit' and waits for acknowledgement before anyone actually commits. That announcement-before-commit is the buffer state the whole protocol turns on.
QuizAll participants vote yes. The coordinator sends preCommit, but a network partition splits the participants: group A receives preCommit, group B doesn't. The coordinator is now unreachable to both. What happens under 3PC's timeout rules?
- Both groups block until the partition heals, so consistency is preserved.
- Group A commits (it reached preCommit) and group B aborts (it didn't) β split-brain, and the transaction ends inconsistent.
- Both groups abort, because losing the coordinator always means abort.
- Group B asks group A over the network and they agree to commit.
Show answer
Group A commits (it reached preCommit) and group B aborts (it didn't) β split-brain, and the transaction ends inconsistent. β This is 3PC's defining failure. Group A reached prepared-to-commit, so its timeout rule says commit. Group B never saw preCommit, so its timeout rule says abort. Both act on locally correct reasoning, but they can't reach each other across the partition to reconcile, so one side commits and the other aborts β atomicity is violated. Option 4 is impossible precisely because a partition means the groups can't communicate. Blocking (option 1) is what 2PC would do; 3PC deliberately chooses to make progress, and that choice is what lets the split-brain happen.
In an interview
Lead with the motivation, not the phase count. 2PC blocks because a participant that voted yes is one step from both commit and abort; if the coordinator dies there, it can't safely guess. 3PC adds a pre-commit phase so that before anyone commits, everyone knows the decision is commit β which gives a stranded participant a safe rule: reached pre-commit means commit, didn't reach it means abort.
Then deliver the honest catch, because that's what separates a memorized answer from an understood one: 3PC is non-blocking only under a fail-stop model. Under a network partition it can split-brain β one side commits, the other aborts β because a timeout can't tell a crashed node from an unreachable one. Add the cost: a third round trip, more messages, and longer lock holds, on every transaction.
Land the conclusion interviewers actually want: this is why modern systems skip 3PC. To get non-blocking commit safely you replicate the decision with a consensus protocol (Paxos/Raft), which decides by majority quorum so at most one side of a partition can act; or you drop atomicity and use a Saga. Naming that the missing ingredient is a quorum, not another phase, shows you understand why consensus won. If pushed, mention Skeen's framing: a protocol is non-blocking only if no state is adjacent to both commit and abort.
References & further reading
- Dale Skeen β Nonblocking Commit Protocols (ACM SIGMOD 1981) β the original paper: the non-blocking condition (no state adjacent to both commit and abort) and the three-phase protocol that satisfies it
- Skeen & Stonebraker β A Formal Model of Crash Recovery in a Distributed System (IEEE TSE 1983) β the state-machine model behind the non-blocking result, and why partitions defeat it
- The Paper Trail β Consensus Protocols: Three-Phase Commit β a clear walkthrough of 3PC's state machine, the recovery rules, and exactly how a partition causes inconsistency
- Gray & Lamport β Consensus on Transaction Commit (2006) β frames atomic commit as consensus and introduces Paxos Commit β the modern, partition-safe replacement for both 2PC and 3PC
- Martin Kleppmann β Designing Data-Intensive Applications, Ch. 9 β the clearest book-length treatment of atomic commit, 2PC's blocking flaw, and why consensus is the real fix
- Three-phase commit protocol β Wikipedia β a compact reference for the canCommit / preCommit / doCommit state transitions and the partition limitation