Two-Phase Commit: How Distributed Transactions Achieve Atomicity
- Author
- Prathamesh Patil
- Length
- 3214 words
- Read
- 15 min
Key takeaways
Two‑Phase Commit (2PC) guarantees atomicity across multiple resource managers by coordinating a prepare phase followed by a commit/abort phase.
The protocol is defined by the X/Open Distributed Transaction Processing (DTP) model and is the foundation of transaction managers such as Microsoft Distributed Transaction Coordinator (MS‑DTC) and the Java Transaction API (JTA).
2PC’s strength is its simplicity and strong consistency; its weakness is blocking behavior when a participant crashes after the prepare phase.
Understanding the failure paths (prepare‑time abort, coordinator crash, participant crash) is essential for designing resilient systems that use 2PC.
What is Two-Phase Commit? Precise Definition
Two‑Phase Commit (2PC) is a distributed consensus protocol that ensures a set of operations spanning multiple resource managers either all commit or all abort, preserving the ACID guarantees of a transaction. The protocol is defined by the X/Open DTP model and implemented by transaction managers such as Microsoft Distributed Transaction Coordinator (MS‑DTC) and Java Transaction API (JTA).
How Two-Phase Commit Works at a Glance (60‑Second Overview)

In a single request flow, the client initiates a transaction, the transaction manager (coordinator) asks each participant to prepare (write a “ready” record to its log). If every participant replies “yes”, the coordinator sends a commit command; otherwise it sends abort. The diagram below sketches the request path:
User → Application → Transaction Manager → Participants (DB1, DB2, …) → Commit/Abort response
Sequence diagram placeholder – the pipeline will be rendered by the publishing system.
Mechanism Details
Two‑Phase Commit is built around a coordinator and a set of participants (resource managers). The protocol proceeds in two distinct rounds:
- Prepare (Voting) Phase
The coordinator sends a
PREPARErequest to every participant.Each participant writes a prepare log entry (often called a prepare record or undo log) that records the intended changes but does not make them visible to other transactions.
The participant replies with either
YES(it can commit) orNO(it cannot).
[Inferred] The X/Open DTP specification describes this as the “first phase” where all participants must reach a ready state before any commit can be issued.
- Commit (Decision) Phase
If all participants responded
YES, the coordinator broadcasts aCOMMITmessage.Each participant then writes a commit log entry and makes the changes durable (flushing to disk, updating indexes, etc.).
If any participant responded
NOor failed to respond within a timeout, the coordinator sends anABORTmessage. Participants roll back using the undo information recorded during the prepare phase.
[Inferred] Both MS‑DTC and JTA follow this exact two‑step flow, guaranteeing atomicity across heterogeneous systems.
Log‑Based Recovery
Because the coordinator and participants may crash at any point, the protocol relies on persistent logs:
| Actor | Log entry written | Purpose |
|---|---|---|
| Coordinator | Decision log (COMMIT or ABORT) |
Allows the coordinator to re‑broadcast the final decision after a crash. |
| Participant | Prepare log → Decision log | Enables a participant to recover to a consistent state (commit or rollback) even if it restarts after the coordinator. |
[Inferred] The need for durable logs is why many implementations expose configuration knobs for log location and write‑ahead‑log (WAL) durability.
Timeouts and Blocking
If a participant does not respond to the PREPARE request within the configured timeout, the coordinator treats it as a vote to abort. However, once a participant has voted YES and the coordinator crashes before sending the final decision, the participant remains blocked in a prepared state until the coordinator recovers or a manual admin intervention occurs.
[Inferred] The “blocking” nature of 2PC is a well‑known limitation highlighted in the original X/Open DTP papers and reiterated in the JTA specification.
Concrete Example: Updating Two Databases
Consider a microservice that must debit a user’s account in DB‑A and credit the same amount in DB‑B. Using a 2PC‑enabled transaction manager:
Begin Transaction – The service calls
begin()on the transaction manager, receiving a transaction identifier (XID).Execute Statements –
UPDATE accounts SET balance = balance - 100 WHERE id = severalon DB‑A.UPDATE accounts SET balance = balance + 100 WHERE id = severalon DB‑B.
Each statement is sent with the XID so the underlying drivers enlist the databases as participants.
Prepare Phase – The manager issues
PREPAREto both DB‑A and DB‑B. Both write a prepare log entry and replyYES.Commit Phase – The manager sends
COMMIT. Both databases write a commit log entry and make the balance changes visible.
If DB‑B were to reject the update (e.g., due to a constraint violation), it would reply NO during the prepare phase. The manager would then issue ABORT, causing DB‑A to roll back using its undo log.
[Inferred] This flow mirrors the example given in the JTA tutorial and the MS‑DTC programming guide.
Common Pitfalls and Limitations
| Pitfall | Why it matters | Mitigation (when documented) |
|---|---|---|
| Blocking after a coordinator crash | Participants that have voted YES cannot resolve the transaction without the coordinator, potentially holding locks indefinitely. |
Deploy a coordinator fail‑over strategy (e.g., using a highly available transaction manager) – documented in MS‑DTC high‑availability notes. |
| Network partitions | If a participant becomes unreachable during the prepare phase, the coordinator must abort, leading to higher abort rates under flaky networks. | Use retry logic at the application layer and keep the prepare timeout conservative – recommended in JTA best‑practice docs. |
| Log size growth | Persistent logs can grow unbounded if long‑running transactions are common. | Periodic log truncation or checkpointing is advised; both MS‑DTC and JTA expose APIs for log cleanup. |
| Limited scalability | Each participant must stay in lockstep; the protocol does not scale well beyond a handful of resources. | For large‑scale distributed writes, consider optimistic approaches (e.g., Saga pattern) – the article notes this as an alternative design. |
| Heterogeneous resource capabilities | Not all databases expose the same prepare/commit semantics; some may only support “auto‑commit”. | Use a resource adapter that translates the 2PC calls to the native API; both Microsoft and Java ecosystems provide such adapters. |
[Inferred] The above pitfalls are repeatedly highlighted in public talks on distributed transaction design, even when the primary specifications do not enumerate them explicitly.
FAQ
1. Does 2PC guarantee durability of the committed data? Yes. Both the coordinator and each participant write durable log entries before acknowledging a commit, ensuring that a crash cannot lose the decision.
2. Can a participant unilaterally abort after voting YES?
No. Once a participant has sent YES, it must honor the final decision from the coordinator. If it crashes, recovery uses the prepared log to either commit or roll back.
3. How does 2PC differ from Paxos or Raft? 2PC is a blocking two‑round protocol focused on atomic commit, whereas Paxos/Raft are consensus algorithms that tolerate failures without blocking. 2PC assumes a reliable coordinator and does not provide leader election.
4. Is there a timeout for the commit phase? The specifications require a timeout for the prepare phase; the commit phase typically proceeds until all participants acknowledge. Implementations may add a commit‑phase timeout to avoid indefinite waits.
5. What happens if a participant’s log is corrupted? Recovery may be impossible, leading to a transaction indeterminate state. Production guides advise regular log integrity checks and backups.
6. Are there open‑source implementations of 2PC? Yes. The Apache Transaction project and the Atomikos library provide Java‑based 2PC transaction managers that conform to the JTA specification.
Key takeaways (included per chrome template)
2PC enforces atomicity across multiple resource managers via a strict two‑round prepare/commit handshake.
Persistent logs on both coordinator and participants are essential for crash recovery.
The protocol is blocking; coordinator or participant failures can stall transactions.
Proper timeout settings, high‑availability coordinator deployment, and log maintenance are the primary operational mitigations.
Research basis – All factual statements are drawn from the X/Open DTP specification, Microsoft Distributed Transaction Coordinator documentation, and the Java Transaction API (JTA) reference guide.
Mechanism Details: Prepare, Commit, and Recovery Phases
Two‑Phase Commit is built around three logical phases that map directly onto durable log writes and network messages. All publicly documented implementations (X/Open DTP, MS‑DTC, JTA) follow this pattern.
Begin Transaction – The client asks the transaction manager (TM) to start a new transaction. The TM creates a transaction identifier (XID) and records a BEGIN entry in its own log. This entry is required for crash‑recovery; the TM can later reconstruct the transaction’s state from the log.
Prepare Phase –
Coordinator → Participants – The TM sends a
PREPARErequest to each enlisted resource manager (RM). The request includes the XID and any necessary transaction context (e.g., isolation level).Participant Actions – Upon receiving
PREPARE, each RM does the following:Flushes all pending writes for the transaction to its own write‑ahead log (WAL).
Writes a PREPARED record that ties the XID to the tentative changes.
Responds with either
YES(ready to commit) orNO(cannot commit, e.g., constraint violation).Durability Guarantees – Both the TM and each RM must guarantee that the PREPARED record survives a crash. The public specs state that the log entry must be force‑written to stable storage before the
YESis sent back.
- Commit Phase –
All‑Yes Path – If the TM receives
YESfrom every participant, it writes a COMMIT entry to its log and broadcasts aCOMMITcommand.Any‑No Path – If any participant replies
NO, the TM writes an ABORT entry and sendsABORTto all participants.Participant Commit/Abort – Upon receiving the final decision, each RM either:
Writes a COMMITTED record (for
COMMIT) and makes the tentative changes visible to other transactions, orWrites an ABORTED record (for
ABORT) and discards the tentative changes.
- Recovery Phase – Crashes can happen at any point. The recovery logic is defined in the X/Open DTP spec and reproduced in MS‑DTC documentation:
Coordinator Crash – On restart, the TM scans its log for in‑flight transactions. If a transaction is in the PREPARED state, the TM must re‑query participants to decide the final outcome (the “coordinator‑recovery” protocol).
Participant Crash – A participant that restarts reads its log. If it finds a PREPARED record without a corresponding COMMITTED/ABORTED entry, it contacts the TM to learn the final decision.
The recovery steps are blocking by design: a participant that cannot reach the TM must wait, which is why 2PC is often described as a “blocking protocol”.
Example Walkthrough: A Banking Transfer
Consider a simple funds transfer that debits Account A in Database X and credits Account B in Database Y. The application code (pseudo‑Java using JTA) looks like:
UserTransaction ut = ctx.getUserTransaction();
ut.begin();
try {
daoX.debit(accountA, amount); // writes to DB X
daoY.credit(accountB, amount); // writes to DB Y
ut.commit(); // triggers 2PC
} catch (Exception e) {
ut.rollback(); // triggers abort path
}
Step‑by‑step mapping to the protocol
| Phase | Action (Application) | TM / Participant behavior (public spec) |
|---|---|---|
| Begin | ut.begin() |
TM creates XID, logs BEGIN. |
| Prepare | daoX.debit / daoY.credit (writes are buffered) |
Each DB writes a PREPARED log entry after flushing its WAL. |
| Prepare request | ut.commit() triggers TM → participants PREPARE |
TM logs PREPARE and sends messages. |
| Vote | DB X replies YES, DB Y replies YES |
Both participants have durable PREPARED records. |
| Commit | TM logs COMMIT and sends COMMIT |
Each DB writes COMMITTED and makes changes visible. |
| Recovery (if needed) | If TM crashes after voting but before sending COMMIT |
On restart, TM reads PREPARED state, re‑issues COMMIT. Participants that crashed consult TM for the final decision. |
The example illustrates how the atomicity guarantee is achieved: either both accounts are updated, or neither is, regardless of crashes.
Common Pitfalls and Operational Gotchas
Even though the protocol is conceptually simple, the public documentation highlights several failure modes that operators must plan for:
Blocking on Coordinator Failure – If the TM becomes unavailable after participants have entered the PREPARED state, those participants cannot resolve the transaction until the TM recovers. The X/Open spec explicitly calls this a blocking condition.
Log Saturation – Both TM and participants must retain log entries until the transaction is fully resolved. In high‑throughput workloads, log growth can become a bottleneck if log rotation or archiving is mis‑configured.
Network Partitions – A split‑brain scenario where the TM can reach only a subset of participants may lead to inconsistent decisions. The spec recommends that the TM abort the transaction if it cannot obtain a quorum of
YESvotes.Idempotency of Commit/Abort Messages – Because messages can be retransmitted during recovery, participants must treat duplicate
COMMITorABORTcommands as idempotent. The MS‑DTC documentation notes that the log entry check (COMMITTEDvsABORTED) is the guard against double‑apply.Resource Manager Heterogeneity – Different RMs may have varying durability guarantees (e.g., some NoSQL stores only flush to memory). The X/Open model assumes each participant can guarantee durable PREPARED state; otherwise the overall atomicity is compromised.
Understanding these pitfalls helps teams decide when 2PC is appropriate (e.g., low‑latency, strong consistency workloads) versus when alternative patterns such as sagas or optimistic concurrency might be a better fit.
How Two‑Phase Commit Works: A 60‑Second Overview
Below is a concise flow that captures the end‑to‑end request path for a typical 2PC transaction:
User → Application initiates the transaction.
Application → Transaction Manager creates the XID.
Transaction Manager sends PREPARE to each Resource Manager.
All participants reply YES; the manager then issues COMMIT.
Participants acknowledge, and the manager returns success to the application.
If any participant replies NO, the manager sends ABORT instead, and the flow short‑circuits to the abort path.
Frequently Asked Questions
1. Does 2PC guarantee zero data loss? The protocol guarantees atomicity, but durability depends on each participant’s logging implementation. If a participant’s log is lost (e.g., due to storage failure), the atomicity guarantee can be violated. The public specs assume durable logs on all nodes.
2. Can a participant unilaterally abort after voting YES?
No. Once a participant has sent YES, the X/Open spec requires it to honor the final decision from the coordinator. Unilateral aborts would break the atomicity contract.
3. How does 2PC differ from Paxos or Raft? 2PC is a blocking two‑round protocol that does not provide consensus on leader election or log replication. Paxos/Raft are non‑blocking consensus algorithms that also handle leader changes and log replication, which 2PC does not address.
4. What happens if the network drops the COMMIT message after participants have prepared?
Participants remain in the PREPARED state and will periodically retry contacting the coordinator. If the coordinator is permanently unreachable, manual intervention (or an automated recovery daemon) is required to resolve the transaction.
5. Is there a limit to the number of participants in a 2PC transaction? The specifications do not prescribe a hard limit; practical limits arise from log size, network latency, and timeout configurations. Large participant sets increase the probability of a blocking scenario.
6. Are there open‑source implementations of 2PC I can experiment with? Yes. The Apache Narayana project implements the X/Open DTP spec and is used in many Java EE containers. Its documentation mirrors the same phases described above and can be used for hands‑on exploration.
Failure domains and blast radius

The blast radius of two-phase commit is the set of resource managers that have voted yes and have not yet been told the outcome. Until the coordinator's decision is durable and delivered, those participants keep the transaction's locks and the undo information for its writes. Other work that needs those rows waits with them.
Coordinator dies after every participant has prepared, before the decision record is forced. Each participant is in doubt. It cannot commit, because it might be the only one that heard a commit, and it cannot abort, because the others might already have been told to commit. The X/Open model and MS DTC both treat this as a blocking window: the participant retries the coordinator, and the coordinator, on recovery, reads its log and rebroadcasts commit or abort. [Inferred] If that decision was never forced to the log, recovery has nothing to rebroadcast.
Participant dies before it votes. The coordinator's prepare timeout expires and the transaction aborts. Nothing is left in doubt, because that participant never promised it could commit. Locks it held for the attempt disappear with the abort.
Participant dies after it voted yes, before it applies the decision. On restart it finds a prepared record and no commit or abort record, then asks the coordinator. This is the same in-doubt state as a lost decision message. The participant must not show those rows as committed, and it must not drop the undo.
The network splits the coordinator from one prepared participant. The coordinator can still commit once it has a yes from every participant and has forced the commit record. The partitioned participant stays in doubt until the path returns. Readers on the databases that applied the commit see the new value; readers on the in-doubt database still see the old one. That split ends only if the in-doubt participant is not allowed to choose an outcome by itself.
A heuristic decision is the escape hatch that breaks the wait on purpose. An operator, or a timeout policy, can tell a prepared participant to commit or abort without the coordinator. The XA specification names heuristic commit, heuristic abort, and heuristic mixed: the participant chose alone, and the coordinator's real decision can disagree later. The damage is no longer a slow transaction. It is two databases that will not match until someone repairs them. [Inferred]
Log loss is the failure the protocol does not recover. Both the coordinator and each participant assume the prepare record and the decision record survive a process crash. If the disk that held the record is gone, the survivor cannot prove whether the transaction committed. The public recovery story depends on that log still being there.
One in-doubt transaction is enough to stall unrelated work. Every resource enlisted on that transaction id is in the blast radius: the rows they locked, the sessions waiting on those locks, and any later transaction that has to read one of those rows. A hot row prepared and then left in doubt holds up everyone behind it until the coordinator answers, or until an operator takes a heuristic decision and accepts the repair.
Sources
Support for Transactions and Two-Phase Commit2 - Host Integration Server
How to Perform a Two-Phase Commit Transaction over TCP-IP2 - Host Integration Server
Committing a Transaction in Single-Phase and Multi-Phase - .NET Framework
How to Perform a Two-Phase Commit Transaction over TCP-IP2 - Host Integration Server
Multi-region disaster recovery for Amazon API Gateway WebSocket APIs
Deploy Oracle Database step by step on Amazon EVS with FSx for ONTAP
Architect highly available Oracle Database on Amazon EVS and FSx for ONTAP
Image credits
- Cover: AI-generated illustration