Two-Phase Commit Protocol
Two-phase commit (2PC) is a distributed coordination protocol that ensures all participants in a distributed transaction either commit or abort together, providing atomicity across multiple databases or services.
What It Really Means
When a single transaction spans multiple databases — debit Account A on Database 1, credit Account B on Database 2 — you need both operations to succeed or both to fail. If Database 1 commits the debit but Database 2 crashes before crediting, money disappears. Two-phase commit prevents this.
The protocol works in two phases. In Phase 1 (Prepare), the coordinator asks all participants: "Can you commit?" Each participant does all the work (writes to disk, acquires locks) and responds "Yes" (vote commit) or "No" (vote abort). In Phase 2 (Commit/Abort), if all participants voted "Yes," the coordinator sends "Commit" and everyone makes the changes permanent. If any participant voted "No," the coordinator sends "Abort" and everyone rolls back.
The key property is that once a participant votes "Yes" in Phase 1, it has promised it can commit — it must hold its locks and resources until the coordinator tells it the final decision. This promise is what makes the protocol blocking: if the coordinator crashes after collecting votes but before sending the decision, all participants that voted "Yes" are stuck, unable to commit or abort, holding their locks indefinitely.
2PC is used in traditional database systems (XA transactions), Google Spanner (with modifications), and PostgreSQL's PREPARE TRANSACTION. Most modern microservice architectures avoid 2PC in favor of the saga pattern due to its blocking nature.
How It Works in Practice
The Protocol in Detail
Phase 1: Prepare (Voting Phase)
- Coordinator sends
PREPARE to all participants
- Each participant:
- Writes all changes to a write-ahead log (WAL)
- Acquires necessary locks
- Responds
VOTE_COMMIT if ready, or VOTE_ABORT if unable
- Coordinator collects all votes
Phase 2: Commit/Abort (Decision Phase)
- If all votes are
VOTE_COMMIT: Coordinator writes COMMIT to its own log, sends COMMIT to all participants
- If any vote is
VOTE_ABORT (or timeout): Coordinator writes ABORT to its log, sends ABORT to all participants
- Each participant executes the decision and acknowledges
The Blocking Problem
Consider this failure scenario:
- Coordinator sends PREPARE to participants A, B, C
- All three vote COMMIT
- Coordinator writes COMMIT to its log
- Coordinator sends COMMIT to A (A commits)
- Coordinator crashes before sending COMMIT to B and C
Now B and C are in limbo. They voted COMMIT and are holding locks, but they do not know the final decision. They cannot commit (the coordinator might have decided to abort). They cannot abort (the coordinator might have decided to commit, and A already committed). They must wait for the coordinator to recover.
This blocking problem is why 2PC is avoided in systems that require high availability.
Real-World: Google Spanner
Spanner uses a modified 2PC where the coordinator is a Paxos group (not a single node), eliminating the single-point-of-failure problem. Each participant is also a Paxos group. If the coordinator leader fails, a new leader is elected from the Paxos group and can complete the protocol. This makes Spanner's 2PC non-blocking in practice, though it adds latency from the consensus protocol.
Real-World: XA Transactions
The XA standard implements 2PC for coordinating transactions across multiple databases. A Java application might use JTA (Java Transaction API) to atomically update both MySQL and PostgreSQL:
XA transactions are supported by most relational databases but are rarely used in modern systems due to performance overhead and the blocking problem.