A transaction updates two resource managers, the first accepts its change, and the second crashes. Returning success would expose half of the operation; returning failure and asking the first manager to undo a committed change may be impossible. The hard question is not how to send both requests. It is how every surviving participant can reach the same final outcome despite messages, processes, and machines failing between any two instructions.

Two-phase commit (2PC) is the classic atomic commitment protocol for that question. Participants first make a durable promise that they can commit, without yet committing. A coordinator then records one decision and broadcasts it. If every vote is yes, the decision is commit; otherwise it is abort. The promise closes the gap in which a participant could vote yes and later forget the work needed to honor that vote.

That guarantee has a price. A prepared participant may be unable to decide anything while the coordinator is unreachable, so locks and other resources remain held. The thesis of 2PC is therefore precise: within a controlled set of transactional resource managers, durable uncertainty can buy an all-or-nothing outcome, but it cannot buy availability during every failure.

Atomic Commitment Is a Narrow Contract

Atomic commitment requires all participants in one transaction to choose the same terminal outcome:

  • Commit means every participant makes its tentative effects permanent.
  • Abort means no participant exposes those effects as committed.
  • A participant must not commit after another valid participant aborts.
  • Once a commit or abort decision is durable, it must never change.

This contract is narrower than many claims attached to “distributed transactions.” 2PC does not decide whether concurrent transactions are serializable; each resource manager must supply the required isolation while work is staged and prepared. It does not make arbitrary HTTP effects transactional. An email service cannot join merely by promising to delete a sent message. It also does not establish a globally available ordering service or solve consensus under arbitrary failures.

The protocol assumes resource managers with durable transaction state. A database participant must be able to stage changes, validate that commit remains possible, persist a prepared record, retain necessary locks, and later commit or roll back by transaction identifier. The coordinator also needs stable storage for the final decision. In XA-style systems, an application-facing transaction manager coordinates databases or message brokers that expose such a prepare/commit interface.

Atomicity also has a defined membership. The coordinator must know which participants belong to the transaction before deciding. Allowing a late participant after all earlier participants have prepared would make their promises refer to a different operation. Dynamic work can occur during the active phase, but the participant set is closed before voting.

Note

The word “atomic” describes the committed outcome, not simultaneous visibility at one physical instant. Participants receive the decision at different times. Correct readers rely on each resource manager’s transaction isolation and commit rules rather than observing the protocol messages directly.

The Protocol Is Two Durable State Machines

Before 2PC begins, the application performs ordinary transactional work at each participant. Those changes remain tentative. The coordinator then drives two phases.

In the prepare phase, it sends PREPARE(transactionId) to every participant. A participant that can guarantee a later commit writes its tentative data and a PREPARED record to durable storage, forces the required log records, retains resources needed to finish, and replies YES. A participant that cannot promise commit records or performs an abort and replies NO. Silence is not a yes vote.

In the decision phase, the coordinator chooses commit only if every participant voted yes. It first forces COMMIT(transactionId) to its own durable log, then sends COMMIT to all participants. Any no vote or prepare timeout leads it to record and send ABORT. Participants durably apply the decision, release locks, acknowledge it, and eventually let the coordinator forget the completed transaction.

sequenceDiagram autonumber participant C as Coordinator participant A as Account shard A participant B as Account shard B C->>A: PREPARE(tx-84) A->>A: Force PREPARED and tentative debit A-->>C: YES C->>B: PREPARE(tx-84) B->>B: Force PREPARED and tentative credit B-->>C: YES C->>C: Force COMMIT(tx-84) C->>A: COMMIT(tx-84) C->>B: COMMIT(tx-84) A->>A: Commit and release locks B->>B: Commit and release locks A-->>C: ACK B-->>C: ACK

The force-before-message ordering is the protocol. If a participant sent YES before its prepared state reached stable storage, a crash could erase the promise. If the coordinator announced commit before its decision was durable, it could recover without knowing that some participant had already committed. Network calls alone do not provide atomicity; recoverable local state around those calls does.

A useful participant transition model is ACTIVE -> PREPARED -> COMMITTED or ACTIVE -> ABORTED, plus PREPARED -> ABORTED when the global decision is abort. There is no valid transition from PREPARED back to ordinary active work. The coordinator moves from COLLECTING to exactly one of DECIDED_COMMIT or DECIDED_ABORT.

Worked Example: A Transfer Across Two Shards

Suppose account A-17 lives on shard A and account B-92 lives on shard B. A transfer of 40 units must debit the first and credit the second. Both balances are database state under one operator, both shards support prepared transactions, and exposing only one side would violate the ledger invariant.

The coordinator assigns tx-84. On shard A it starts a local transaction, verifies sufficient funds, and stages balance = balance - 40. On shard B it stages balance = balance + 40. Neither change is externally committed yet. It then asks both shards to prepare.

Shard A checks constraints and conflicts, writes the changed page information and PREPARED tx-84 to its transaction log, and keeps the account row locked. Shard B does the equivalent. When both yes votes arrive, the coordinator forces DECISION tx-84 COMMIT. From that moment, abort is no longer a legal recovery shortcut. It can retry commit delivery until both shards acknowledge.

Consider four interruptions:

Interruption Durable facts Correct recovery
Shard B rejects the credit before voting A may be prepared; B voted no Coordinator records abort and tells A to roll back
Coordinator crashes before recording a decision Participants may have voted yes Recover coordinator state; participants remain uncertain
Coordinator crashes after forcing commit but before notifying B Commit decision exists; A may have committed Recover and resend commit to both participants
A commits but its acknowledgement is lost A is committed; coordinator still lists it pending Resent commit returns success; the command is idempotent

The transaction identifier is essential. Every retry addresses the same durable transaction, so COMMIT tx-84 cannot create a second debit. Messages may be duplicated, reordered, or delayed; terminal operations must return the existing outcome when repeated.

This example is appropriate for 2PC because the invariant is strict, the resource managers implement the protocol, and the operation is expected to be short. If shard B represented an independently operated payment provider with no prepare interface, placing an HTTP authorization inside this protocol would not make it a participant. The boundary is defined by capabilities, not by where application code draws a box.

Prepared Transactions Explain the Blocking Cost

The uncomfortable state is not active or committed; it is prepared and uncertain. After voting yes, a participant has promised to obey a decision that it may not yet know. It cannot commit independently because another participant may have voted no. It cannot abort because the coordinator may already have durably chosen commit and told someone else.

Therefore a participant that cannot contact the coordinator must wait, potentially holding row locks, uniqueness reservations, transaction identifiers, log space, or pinned old versions. Other work that needs those resources may block behind it. A single uncertain transaction can expand into latency, deadlocks, and capacity pressure far beyond the original request.

Participants can sometimes run a termination protocol by asking peers what they know. Learning that any participant has committed proves the global decision was commit; learning a durable abort proves abort. But if every reachable participant is merely prepared, they still cannot infer the decision. The coordinator could have recorded commit immediately before becoming unreachable. Classic 2PC is blocking in exactly this indistinguishable state.

Replicating the coordinator’s decision log with a consensus protocol reduces the chance that one coordinator process or disk makes the decision unavailable. A new leader can read the replicated decision and continue notification. That improves fault tolerance, but it does not turn 2PC into a non-blocking protocol under every partition: if participants cannot reach a quorum that knows the decision, uncertainty remains. Consensus chooses and preserves coordinator state; 2PC collects resource-manager votes and applies the atomic outcome.

Warning

Never configure a prepared-transaction timeout that unilaterally aborts after a yes vote unless the entire protocol defines that behavior before preparation. Guessing abort locally can conflict with a commit decision that already exists elsewhere.

Recovery Depends on Log Records and Ordering

On restart, the coordinator scans its transaction log. A durable commit or abort record is authoritative and is resent until every participant is known to have completed. A transaction with no final decision needs policy based on what was durably recorded before the crash. The coordinator must not reconstruct an outcome from volatile vote counters.

A restarting participant scans for prepared-but-incomplete transactions. It restores enough tentative state and locks to finish, then queries the coordinator or designated recovery service using the global transaction identifier. Committed and aborted records make repeated decision messages harmless. Log truncation must retain the mapping until no delayed participant can reasonably ask for the outcome.

Many implementations optimize logging with a presumed outcome:

  • Presumed abort treats the absence of a coordinator record as abort for transactions that never reached a durable commit decision. Abort records and acknowledgements can often be reduced. It suits workloads where aborts or early failures are common.
  • Presumed commit records more evidence for the commit path and can reduce commit completion work, but it requires explicit durable information before absence can imply commit. It may suit commit-heavy environments with different recovery economics.

These are not licenses to infer an outcome from silence. They are complete protocol variants whose log-force and forgetting rules make absence meaningful. Coordinator and participant implementations must agree on the variant.

A heuristic decision is an operator or resource manager forcing commit or rollback without the authoritative global result, usually to free resources during a prolonged outage. It sacrifices atomicity and can require manual reconciliation. Systems should expose it as a severe, audited exception, not as routine timeout handling. The recovery interface should show transaction identity, age, participants, known votes, coordinator decision if any, and locks held before anyone considers such an action.

Isolation and Capacity Still Matter

Preparation makes a transaction finishable; it does not make it cheap. Local isolation may retain locks from the first data access through global completion. Slow prepare calls, overloaded participants, coordinator retries, and network partitions all extend that interval. Cross-resource deadlocks are also possible when two global transactions acquire participants or rows in opposite orders, while no one local lock manager sees the complete wait-for graph.

Keep the active and prepared windows short. Do validation and remote computation before entering the distributed transaction when correctness permits. Enlist only necessary participants, use a deterministic acquisition order, bound ordinary request waiting, and reject new work before a saturated coordinator creates thousands of prepared transactions. A client timeout must not cancel protocol recovery: once commit is decided, background completion continues even if the caller no longer waits.

Useful operational measures include active, preparing, prepared, and resolving transaction counts; age of the oldest prepared transaction; prepare and decision latency; coordinator recovery duration; decision resend rate; locks or sessions retained by prepared work; and heuristic outcomes. Alert primarily on uncertain age and resource impact. A transient packet loss that is retried immediately matters less than a prepared transaction blocking a hot key.

Large transactions amplify every cost. They generate more tentative data and log traffic, take longer to force, hold more locks, and make recovery heavier. Batching a vast migration into one 2PC transaction may provide an attractive all-or-nothing story while making forward progress fragile. Smaller idempotent units or a staged cutover can be operationally safer even when the protocol is available.

Choose 2PC or a Saga by the Required Truth

2PC and Sagas answer different questions. In 2PC, participants delay local commit until one protocol decision. There is no business-visible sequence to compensate during a successful protocol. In a Saga, each step commits locally and later failure triggers new compensating or forward-recovery actions. Intermediate states are part of the business model.

Decision factor Two-phase commit Saga
Required outcome One atomic commit or abort Eventual workflow completion or compensation
Participant capability Prepare, durable transaction ID, commit/rollback Local transaction plus idempotent commands/events
Failure posture May block while outcome is uncertain Intermediate states remain visible and recoverable
Isolation Can retain database locks until decision Uses semantic states; no global isolation by default
Ownership Usually tightly controlled infrastructure Often independent services and data owners
Duration Short transactions Can span long business processes
Undo meaning Protocol rollback before global commit New domain action after earlier commits

Prefer one local database transaction when one resource can own the invariant. Prefer 2PC when atomicity across a small, controlled participant set is mandatory, every resource manager supports correct preparation, transaction duration is bounded, and temporary unavailability is preferable to partial commit. Prefer a Saga when participants cannot prepare, business steps are long-running, autonomy matters, and honest compensations or forward recovery exist.

Other designs can remove the choice. Co-locating data, routing related records to one shard, recording intent in one authoritative ledger, or serializing commands through one owner may preserve the invariant without a distributed commit. The best atomic commitment protocol is often a data boundary that does not need one.

Verify Every Crash Boundary

A happy-path integration test proves little. The critical checks stop and restart components between every durable write and every message: before a participant forces PREPARED, after it forces but before it sends YES, after the coordinator receives the final vote, after it forces the decision, after one participant commits, and before each acknowledgement. On every restart, assert that all participants eventually reach the same terminal outcome.

Use deterministic fault injection for dropped, duplicated, delayed, and reordered messages. Verify that duplicate prepare and decision commands are idempotent, a no vote can never lead to commit, a durable commit can never become abort, and coordinator log replay keeps retrying incomplete notifications. Run tests against the real storage engine because mocks rarely reproduce prepared locks, log flushing, session loss, or recovery behavior.

Exercise partitions long enough to observe blocking. Confirm admission control works, alerts identify the exact prepared transaction, and unrelated keys can still progress where isolation permits. Kill the coordinator host, not only its process, and test loss of the current leader when its decision log is replicated. Restore from backups only in an isolated drill: a participant restored behind the coordinator’s forgetting horizon can resurrect an in-doubt transaction whose decision record has already been discarded.

Operational runbooks should distinguish “no decision yet” from “decision known but not delivered.” The former may require restoring coordinator availability; the latter requires safe retransmission. Record manual actions immutably and reconcile every participant after any heuristic resolution.

Takeaways

  • 2PC obtains atomic commitment by making participants durably prepare before one coordinator decision.
  • The force-before-message order is essential: votes and decisions must survive the crashes their messages can outlive.
  • A prepared participant cannot safely guess while the decision is unreachable, which makes classic 2PC a blocking protocol.
  • Replicating coordinator state improves recovery but does not remove every partition-induced wait.
  • Local isolation, deadlock handling, capacity limits, and observability remain part of correctness.
  • 2PC fits short transactions among controlled resource managers; Sagas fit long workflows with committed intermediate states and business compensation.
  • Crash-point testing and an explicit in-doubt transaction runbook are mandatory before relying on atomic commitment.

Two-phase commit is neither obsolete magic nor a universal transaction layer. It is a sharp protocol with an honest exchange: participants preserve the option to commit, operators accept that uncertainty may block progress, and durable logs ensure that recovery completes the same decision everywhere.