Two-Phase Commit (2PC)
In plain English
Plain definition
Two-Phase Commit gets multiple databases to agree on whether a transaction succeeds or fails. A coordinator asks everyone to vote, and only commits the transaction everywhere if every participant agrees; if the coordinator crashes at the wrong moment, participants can be stuck holding locks with no way to proceed.
2PC operates in two synchronous rounds against a set of Resource Managers (RMs) orchestrated by a Transaction Manager (TM). In the Prepare (Voting) Phase, the TM broadcasts a PREPARE message; each RM performs the actual work, writes an undo/redo record to its local WAL, and responds VOTE-COMMIT or VOTE-ABORT. Crucially, a VOTE-COMMIT constitutes a durable promise — the RM must guarantee it can commit later regardless of crashes, meaning locks on the affected rows/keys stay held. In the Commit Phase, if all votes are affirmative, the TM persists a commit record and broadcasts GLOBAL-COMMIT; a single dissent triggers GLOBAL-ABORT to all parties.
The protocol’s defining architectural liability is the blocking problem. If the TM crashes after collecting all votes but before broadcasting the outcome, every participant that voted COMMIT is stuck holding exclusive locks indefinitely — it cannot unilaterally commit (peers may have voted abort) nor abort (peers may have already committed). This is a fundamental limitation, not an implementation bug: 2PC has no non-blocking solution in the presence of coordinator failure without additional assumptions (this motivates Three-Phase Commit and Paxos Commit variants, which trade extra round trips for termination guarantees under certain failure models).
- Presumed Abort/Commit optimisations: Production implementations (e.g., XA transactions in JTA, Spanner’s participant leaders) avoid logging every ACK by defaulting unknown transaction states to abort, cutting log I/O roughly in half.
- Coordinator recovery: A restarted TM must replay its transaction log; any transaction lacking a final decision record is unilaterally resolved to
ABORTunder presumed-abort semantics, which is why RMs block only up to the TM’s log recovery, not indefinitely in well-implemented systems — but that window is still an availability outage. - Heuristic outcomes: Some XA implementations allow an RM to unilaterally time out and resolve a stuck prepared transaction (a heuristic commit/abort). This breaks atomicity guarantees and requires manual reconciliation — a well-known operational scar in legacy middleware.
In modern cloud-native architectures, 2PC is largely avoided at the application tier in favour of the Saga Pattern or event-driven eventual consistency, precisely because the lock-holding, blocking-on-coordinator-failure characteristic is incompatible with horizontal scaling and multi-region deployment. It persists internally, however, inside systems that need genuine cross-shard atomicity with strict serializability — Spanner’s cross-Paxos-group transactions and CockroachDB’s parallel commit variant both implement 2PC-derived protocols underneath a single-statement SQL interface, but layer Raft/Paxos beneath the coordinator role specifically to eliminate the single-point-of-blocking failure mode described above.