How Does Two-Phase Commit Coordinate Transactions?
The Two-Phase Commit protocol is a classical distributed algorithm that ensures all participating database nodes either commit or abort a transaction together. By splitting the commitment process into a voting phase and a decision phase, a central coordinator guarantees atomicity across independent machines even when individual nodes or network links fail.
Roles in the Protocol
In a distributed database, a single logical transaction often modifies records stored on \(n\) separate nodes, known as participants or cohorts. Because each node manages its own local storage and write-ahead log, nodes cannot independently decide whether to make changes permanent without risking partial updates.
To solve this, the protocol designates one node as the coordinator for the transaction:
- Coordinator: Orchestrates the protocol, collects votes from all participants, makes the final commit or abort decision, and broadcasts that decision.
- Participants: Execute the local transaction work, acquire necessary locks, write durability records to disk, and follow the coordinator's final instruction.
Phase 1: The Prepare (Voting) Phase
When the client application requests to commit a distributed
transaction, the coordinator initiates Phase 1 by sending a
PREPARE message to all \(n\) participants.
Upon receiving PREPARE, each participant checks whether
it can guarantee the durability and consistency of its local
changes:
- It verifies that no local integrity constraints or concurrency rules have been violated.
- It forces all undo and redo log records for the transaction to stable storage (disk).
- If both steps succeed, the participant enters the
prepared (or uncertain) state and
replies with
VOTE_COMMIT. Otherwise, it aborts locally, releases locks, and replies withVOTE_ABORT.
Once a participant votes VOTE_COMMIT, it surrenders its
autonomy. It must hold all locks on the modified data until the
coordinator issues a final decision.
Phase 2: The Commit (Decision) Phase
The coordinator waits for responses from all \(n\) participants. Its decision follows a strict logical rule:
\[\text{Decision} = \begin{cases} \text{COMMIT} & \text{if } \forall i \in \{1, \dots, n\},\ \text{vote}_i = \text{VOTE\_COMMIT} \\ \text{ABORT} & \text{if } \exists i \in \{1, \dots, n\},\ \text{vote}_i = \text{VOTE\_ABORT} \text{ or timeout} \end{cases}\]
Before sending the decision over the network, the coordinator writes
the outcome to its own local log so it can recover the decision if it
crashes. It then sends either a GLOBAL_COMMIT or
GLOBAL_ABORT message to every participant.
Each participant receives the decision, writes a corresponding commit
or abort record to its local log, releases its held locks, and sends an
ACK back to the coordinator. Once the coordinator receives
all acknowledgments, it marks the transaction complete.
Handling Failures and the Blocking Problem
Write-ahead logging allows nodes to recover from crashes without
losing atomicity. If a participant crashes before voting, it aborts upon
recovery, and the coordinator's timeout triggers a
GLOBAL_ABORT. If a participant crashes after voting
VOTE_COMMIT, it remains in the uncertain state upon restart
and queries the coordinator for the final decision.
However, Two-Phase Commit is a blocking protocol. If
the coordinator crashes after participants have voted
VOTE_COMMIT but before it sends the final decision, those
participants cannot safely commit or abort on their own. They must
remain blocked, holding their locks and keeping data inaccessible, until
the coordinator recovers.
Performance and Trade-Offs
Because every transaction requires two full network round-trips and multiple synchronous disk flushes, Two-Phase Commit introduces significant latency. In classical distributed systems, it is best suited for workloads that require strict ACID atomicity across a small number of reliable nodes, whereas high-throughput systems often favor non-blocking consensus protocols like Paxos or Raft for replication, or eventual consistency patterns like Sagas.