Suppose one business operation must update two independent transactional resources. Writing to the first and then the second creates an uncomfortable failure case: the first write may commit while the second fails. Reversing the order only moves the problem.

Two-phase commit, usually shortened to 2PC, is a coordination protocol for making one commit-or-abort decision across multiple participants that can each prepare and commit a local transaction. Its purpose is atomicity across those participants: under the protocol’s assumptions, they do not intentionally finish with some participants committed and others aborted for the same transaction.

That guarantee has a cost. Participants may need to hold transactional resources while coordination is unresolved, and some failures can leave them unable to decide independently. This article explains the protocol step by step, where its safety comes from, and when a different design is a better fit.

Start with the problem a local transaction cannot solve

Imagine an internal operation that must reserve inventory in one transactional service and record a corresponding allocation in another:

inventory.reserve(orderId, item)
allocation.create(orderId, item)

Assume each operation can run inside its own local transaction. That protects each resource individually, but it does not make the pair atomic.

If the inventory transaction commits and the allocation call then fails, the system has a reservation without its matching allocation. A local rollback cannot undo a transaction that has already committed in another participant.

The missing capability is a shared decision point:

Can every participant promise that it is able to commit before any participant is told to commit?

Two-phase commit creates that decision point.

Separate readiness from the final decision

2PC has a coordinator and two or more participants. The coordinator drives one logical transaction. Each participant controls its own local transactional resource.

The protocol separates the work into two phases.

Phase 1: prepare

The coordinator asks every participant to prepare the transaction.

Coordinator        Inventory        Allocation
    |                   |                |
    |---- PREPARE ----->|                |
    |---- PREPARE ---------------------->|
    |<------ YES -------|                |
    |<------ YES ------------------------|

A YES vote is stronger than “the request looks valid.” The participant must reach a state from which it can later honor a commit decision, even if there is a failure between the two phases. In a durable implementation, that normally means the participant has recorded enough transaction state durably and retains whatever transactional guarantees are required until it learns the outcome.

If a participant cannot make that promise, it votes NO.

Phase 2: decide

If every participant votes YES, the coordinator decides COMMIT. If any participant votes NO, or the prepare phase cannot complete according to the coordinator’s policy, the coordinator decides ABORT.

The decision is then sent to the participants:

Coordinator        Inventory        Allocation
    |                   |                |
    |---- COMMIT ------>|                |
    |---- COMMIT ----------------------->|
    |<----- ACK --------|                |
    |<----- ACK -------------------------|

A participant that prepared successfully does not make a new business decision in phase 2. It follows the coordinator’s final outcome.

The prepare promise is the core of the protocol

It is easy to describe 2PC as “ask everyone, then commit everyone,” but that wording hides the important guarantee.

Consider a participant that answers YES and then releases the lock protecting the data it plans to update. Another transaction changes that data before the final COMMIT arrives. The participant may no longer be able to perform the operation it promised.

The prepare phase therefore needs a durable commitment to the pending transaction, not merely a validation check.

A useful mental model is:

YES to PREPARE = "If the final decision is COMMIT, I can still commit this transaction."

This promise is what lets the coordinator collect votes before choosing the global outcome.

The exact mechanism depends on the transactional system. Locks, transaction logs, recovery records, and isolation behavior are implementation concerns rather than properties that application code should invent casually.

Walk through a successful transaction

Assume inventory and allocation both participate in 2PC.

The operation begins and each participant performs its local work without making it externally final. The coordinator then starts the prepare phase.

  1. Inventory prepares and votes YES.
  2. Allocation prepares and votes YES.
  3. The coordinator records the global COMMIT decision durably.
  4. The coordinator sends COMMIT to both participants.
  5. Each participant commits locally and acknowledges the decision.

The important boundary is step 3. Once the coordinator has durably chosen COMMIT, a temporary failure while delivering that message does not turn the transaction into an abort. Recovery must continue trying to bring prepared participants to the recorded outcome.

That is different from ordinary request handling, where a timeout often means the caller can simply report failure. In a transaction protocol, “I did not receive the reply” is not the same as “the transaction aborted.”

A negative vote makes abort straightforward

Now suppose allocation discovers during prepare that it cannot reserve the required state and votes NO.

Inventory:   YES
Allocation:  NO

Global decision: ABORT

The coordinator tells all participants to abort. Inventory can discard its prepared work, and allocation does not commit its attempted change.

This is why the coordinator must not issue COMMIT after only some positive votes. Atomicity depends on obtaining the required agreement before choosing commit.

Failures reveal the main trade-off

The difficult case is not a clean NO. It is uncertainty after a participant has prepared.

Suppose inventory votes YES, records its prepared state, and then loses contact with the coordinator before receiving the final decision. Inventory knows it promised to commit if instructed, so it cannot safely decide to abort on its own. The coordinator may already have durably decided COMMIT.

Likewise, it cannot safely commit merely because it voted YES; another participant may have voted NO, causing the coordinator to choose ABORT.

The participant is therefore in doubt until it can learn the global decision through the protocol’s recovery mechanism.

During that interval, resources associated with the prepared transaction may remain unavailable to conflicting work. This is the classic availability cost of two-phase commit: it can preserve the atomic decision while losing progress during certain failures.

2PC is commonly described as a blocking protocol for this reason. That does not mean every failure stops the entire system. It means a prepared participant can encounter a failure scenario in which it cannot determine the safe outcome by itself.

Timeouts do not create a new truth

A common mistake is to treat a timeout as permission to invent an outcome.

Imagine the coordinator sends COMMIT, the participant commits, but the acknowledgement is lost. The coordinator observes a timeout. If it now decides that the participant must have failed and switches the transaction to ABORT, it can contradict an outcome that already happened.

Protocols avoid this by distinguishing decision state from message delivery state.

Once a global decision is durable, retries are about delivering or discovering that decision. They are not repeated votes on whether the transaction should commit.

This distinction is useful far beyond 2PC: distributed systems often need to represent “outcome unknown to this process” separately from “operation definitely failed.”

Recovery state must survive process restarts

In-memory coordinator state is not enough for a durable 2PC implementation.

Suppose every participant votes YES, the coordinator sends COMMIT to inventory, and then the coordinator process crashes before contacting allocation. After restart, the coordinator must know that the transaction’s decision was COMMIT; choosing again could produce a different answer.

For the same reason, a participant that restarts after preparing must know that it has an unresolved prepared transaction rather than silently treating the work as aborted.

A production transaction manager therefore needs durable protocol state and recovery behavior. The details vary by platform, but the engineering requirement is stable: a process restart must not erase information needed to preserve the already-made transaction decision.

2PC does not make arbitrary side effects transactional

Two-phase commit only coordinates participants that implement the required transactional protocol.

Sending an ordinary email, calling an unrelated HTTP endpoint, or writing to a system with no prepare/commit capability does not become atomic merely because those actions are placed near a 2PC transaction in code.

For example:

begin distributed transaction
update account
send email          # ordinary external side effect
commit transaction

If the email system is not a transaction participant, the database transaction cannot roll back a message that has already been delivered.

This boundary matters when evaluating a design. Ask which resources actually participate in the atomic protocol rather than assuming every step in the business workflow inherits the transaction’s guarantees.

Atomicity is not the same as availability

2PC solves a consistency problem by coordinating a single outcome. It does not make dependencies more available, reduce latency, or remove failure handling.

Compared with independent local transactions, a distributed commit usually adds coordination messages and extends the time during which local transactional state may need to remain pending. The practical cost depends on the transaction manager, storage systems, workload, network, and failure rate, so a universal performance multiplier would be misleading.

The design question is therefore not “Is 2PC good or bad?” It is:

Is one atomic commit decision across these specific transactional resources worth the coordination and failure-mode costs?

That question should be answered from the business invariant and operational environment.

Use 2PC when the invariant requires one outcome

Two-phase commit can be appropriate when all required resources support compatible distributed transactions and partial commit would violate an invariant that the system is not willing to expose or repair later.

Examples can include tightly controlled enterprise systems where a transaction manager and participating resource managers are already part of the platform. In that environment, using the established transaction mechanism may be simpler and less error-prone than building application-level compensation logic.

Even then, keep distributed transactions focused. Long-running user workflows are poor candidates because holding prepared transactional state while waiting for human input or slow external work increases contention and failure exposure.

Prefer simpler boundaries when they satisfy the requirement

Not every multi-step operation needs distributed atomicity.

If two updates can live in the same transactional resource, one local transaction usually avoids distributed coordination entirely. Sometimes changing the ownership boundary is simpler than coordinating separate owners.

If temporary inconsistency is acceptable, an asynchronous workflow can let each service commit locally and communicate progress through durable messages. Such a design needs its own treatment of duplicate delivery, retries, ordering, and failure recovery; it is not automatically simpler. Its advantage is that participants do not have to remain prepared while waiting for one global commit decision.

If a completed step can be meaningfully reversed, a workflow may use explicit compensating actions instead of pretending that every side effect can participate in one atomic transaction. Compensation is a business operation, not a magical rollback: it may fail, require retries, and have different semantics from erasing history.

These alternatives provide different guarantees. Choose among them by first stating the invariant you actually need.

Avoid common implementation misunderstandings

Several mistakes make discussions of 2PC confusing:

  • Treating prepare as validation only. A positive vote must leave the participant able to honor the later decision.
  • Treating timeout as abort. After prepare, missing communication can mean the outcome is unknown locally, not that no commit occurred.
  • Forgetting durable recovery state. Coordinator and participant restarts must preserve protocol decisions and unresolved prepared work.
  • Including non-participants in the guarantee. Ordinary external side effects are outside the atomic boundary unless they genuinely implement the transaction protocol.
  • Using 2PC for long business workflows. The protocol is designed around transactional participants, not indefinite coordination across humans and arbitrary services.

These are not minor implementation details. They follow directly from the safety guarantee the protocol is trying to provide.

A practical decision checklist

Before choosing distributed commit, answer these questions:

  1. What exact invariant would partial commit violate?
  2. Can the required writes be placed in one local transaction instead?
  3. Do all required resources actually support the transaction protocol?
  4. What state can remain locked or otherwise pending after prepare?
  5. How does coordinator recovery discover a previously recorded decision?
  6. How does a recovering participant resolve an in-doubt transaction?
  7. Are the expected coordination latency and failure behavior acceptable for this workload?
  8. Would an asynchronous workflow or explicit compensation satisfy the business requirement with a better operational trade-off?

If these questions do not have clear answers, adding a distributed transaction manager will not make the uncertainty disappear. It will move that uncertainty into a protocol whose guarantees and recovery behavior still need to be understood.

Conclusion

Two-phase commit creates one atomic commit-or-abort decision across transactional participants by separating prepare from decision. Participants first promise that they can honor a later commit; only after the required positive votes does the coordinator choose and record the global outcome.

The same mechanism that provides the guarantee explains the cost. A prepared participant cannot safely invent an outcome when communication fails, so it may need to wait for recovery while transactional resources remain pending.

Use 2PC when the business invariant genuinely requires one atomic outcome and the participating systems provide the necessary transaction support. When a local transaction, asynchronous workflow, or explicit compensation satisfies the requirement, the simpler failure model is often the more practical design.