Guide contents

Lesson 43 of 129

Distributed Transactions: Two-Phase and Three-Phase Commit

A distributed transaction updates data on more than one node (different databases, shards, or services) and must be atomic: either every participant commits or every participant aborts. The problem is called atomic commitment. It is hard because any node or message can fail at any point, and a participant that has voted to commit cannot later change its mind on its own.

Two-phase commit (2PC)

2PC uses one coordinator and several participants (also called resource managers or cohorts). It is implemented by the XA standard, by database engines for cross-shard transactions, and by systems such as Google Spanner (2PC across Paxos groups).

Phase 1: prepare (voting)

  1. The coordinator sends PREPARE to all participants.
  2. Each participant checks that it can commit: locks acquired, constraints satisfied, changes durably written to its log. It writes a prepared record to its write-ahead log, then replies YES or NO.
  3. After voting YES, a participant is in doubt: it must keep its locks and must not commit or abort until it learns the decision.

Phase 2: commit or abort

  1. If all participants voted YES, the coordinator writes a commit record to its own durable log (the commit point) and sends COMMIT to all. If any voted NO or timed out, it logs abort and sends ABORT.
  2. Each participant applies the decision, releases locks, and sends an acknowledgment.
  3. The coordinator can then forget the transaction.
sequenceDiagram
    participant C as Coordinator
    participant P1 as Participant 1
    participant P2 as Participant 2
    C->>P1: PREPARE
    C->>P2: PREPARE
    P1-->>C: YES (prepared, locks held)
    P2-->>C: YES (prepared, locks held)
    Note over C: log COMMIT (commit point)
    C->>P1: COMMIT
    C->>P2: COMMIT
    P1-->>C: ACK
    P2-->>C: ACK

Participant state machine

stateDiagram-v2
    [*] --> Working
    Working --> Prepared: PREPARE received, vote YES
    Working --> Aborted: vote NO or PREPARE timeout
    Prepared --> Committed: COMMIT
    Prepared --> Aborted: ABORT
    Committed --> [*]
    Aborted --> [*]

The Prepared state is the dangerous one: the participant can leave it only through a message from the coordinator (or by asking other participants).

In the cooperative termination protocol, an in-doubt participant asks the others for the outcome. If any participant has received COMMIT or ABORT, or has not yet voted (and therefore can abort), the asker learns the outcome. If every reachable participant is also in Prepared, none can decide and all remain blocked.

Failure cases

Failure Outcome
Participant fails before voting Coordinator times out and aborts.
Participant fails after voting YES On restart it reads its log, finds prepared, and asks the coordinator for the decision.
Coordinator fails before deciding Participants that have not voted can abort. Participants in Prepared must wait.
Coordinator fails after logging the decision, before sending it Participants stay in doubt until the coordinator recovers and resends.
Message loss Handled by timeouts and retries. COMMIT and ABORT must be idempotent.

Blocking

2PC is a blocking protocol. If the coordinator crashes after participants voted YES and before they learn the outcome, those participants cannot safely decide alone. If they commit, another participant may have been told to abort. If they abort, the coordinator may have decided to commit. They must hold locks until the coordinator recovers. This can stall other transactions that need the same rows.

Consequences:

  • Availability depends on the coordinator. It is a single point of failure for in-doubt transactions unless its log is replicated (Spanner replicates the coordinator's state with Paxos, which removes this weakness).
  • Lock hold time. Locks are held across at least two network round trips plus disk forces at every participant.
  • Latency. At least two sequential message rounds and multiple synchronous log writes per transaction.
  • Heuristic decisions. Operators sometimes force an in-doubt participant to commit or abort manually, which risks inconsistency.

Optimizations include presumed-abort and presumed-commit (fewer log writes and messages in the common case) and read-only participants that skip phase 2.

Three-phase commit (3PC)

3PC adds a middle phase so that no participant is in a state from which it cannot infer the outcome, making the protocol non-blocking under crash failures and a reliable network.

  1. CanCommit: coordinator asks; participants vote.
  2. PreCommit: if all voted yes, the coordinator sends PRECOMMIT. Participants acknowledge. A participant in PreCommit knows every participant voted yes.
  3. DoCommit: coordinator sends COMMIT.

If the coordinator fails, participants elect a new coordinator, which polls their states. If any participant has committed, the outcome is commit. If any participant is in PreCommit, the new coordinator completes the pre-commit and commits. If all are still uncertain (voted yes, no PRECOMMIT), it aborts, because no participant can have committed before every participant received PRECOMMIT. Timeouts drive failure detection.

Limitations of 3PC

  • It assumes a synchronous network with bounded message delay and no partitions. In a partition, one side may time out and commit while the other times out and aborts, breaking atomicity.
  • It adds another round trip, so latency increases.
  • It is rarely used in practice. Systems that need non-blocking atomic commit generally use 2PC where the coordinator and participants are themselves replicated with consensus, such as Paxos Commit or Spanner.

Why not use distributed transactions across services

Microservices normally own their data (see database per service). Cross-service 2PC is avoided because:

  • Locks span service boundaries, coupling availability and latency of every service in the transaction. If participant failures are independent, the availability of the transaction is roughly the product of the participants' availabilities.
  • Many stores and message brokers do not support XA or a prepare phase (most NoSQL databases, HTTP APIs, SaaS providers).
  • Long-running business processes cannot hold locks for seconds or minutes.
  • The coordinator becomes a shared component that couples teams.

The alternative is the saga pattern: a sequence of local transactions, each committing immediately, with compensating transactions that semantically undo earlier steps on failure. Sagas give up isolation and atomicity in the strict sense (intermediate states are visible) in exchange for availability and loose coupling. Reliable event publishing alongside a local transaction is handled by the transactional outbox.

Comparison

Property 2PC 3PC Saga
Atomicity Yes Yes (with assumptions) Eventual, via compensation
Isolation Yes, through locks Yes, through locks No (needs countermeasures)
Blocks on coordinator failure Yes No, under crash-stop and no partitions No
Message rounds 2 3 One local transaction per step
Partition tolerant Safe but blocks Unsafe Available
Typical use Within one database or replicated coordinator Mostly theoretical Cross-service business processes

Interview questions

  • Why is 2PC called a blocking protocol? A participant that voted YES cannot commit or abort on its own. If the coordinator fails before delivering the decision, it must hold its locks until the coordinator recovers.
  • What is the commit point in 2PC? The moment the coordinator durably logs the commit decision. Before it, the transaction can still abort. After it, the outcome is commit even if the coordinator crashes.
  • How does 3PC avoid blocking, and why is it not widely used? The pre-commit phase guarantees that any participant that has not seen PRECOMMIT can abort and any that has can commit. This relies on bounded delays and no partitions, which real networks do not guarantee, and it costs an extra round trip.
  • What should a service do instead of 2PC across microservices? Use a saga with compensating actions, and use idempotent steps and an outbox for reliable messaging, accepting eventual consistency.