Skip to main content
KBY Technologies Logo
cd ../lexicon
sys/docs/lexicon/two-phase-commit-2pc.md
Lexicon Entry

Two-Phase Commit (2PC)

A blocking atomic commitment protocol that ensures all participants in a distributed transaction either commit or abort together, coordinated by a single transaction manager.

Practical example

XA transactions in Java EE coordinate a JMS queue and a relational database so that a message is only consumed if the corresponding database write commits, using 2PC under the hood via the Transaction Manager.

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 optimizations: 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 ABORT under 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 favor 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.