Sign in
TodayMapLearnPracticeReview
Library
14 MINexpertDistributed SystemsNot started

Two-Phase Commit

Two-Phase Commit (2PC) is the classical protocol for atomic commitment across multiple resources. A coordinator asks every participant to PREPARE in phase one; if all vote YES, the coordinator sends COMMIT in phase two. The protocol guarantees atomicity (all commit or all abort) but is blocking — if the coordinator crashes between phases, participants hold locks indefinitely waiting for a decision they cannot make on their own. Three-phase commit (3PC) removes the blocking but adds a round trip and requires bounded network delays. In practice, microservices have largely abandoned 2PC in favor of the saga pattern.

Why this matters

2PC is the textbook answer to 'how do I make a transaction span multiple databases?' and the answer most candidates give in interviews. But it is also the protocol that nobody wants to operate in production across services, because every coordinator crash blocks the system and the recovery story is painful. Understanding 2PC deeply — its phases, its blocking failure modes, the recovery protocols, and why 3PC tries (and largely fails) to fix it — is the foundation for understanding why sagas exist. The lesson: atomicity is achievable in distributed systems, but only at the cost of availability; the modern microservices stack has decided that availability wins.

Prerequisites
  • Distributed Transactions
Related
  • Saga Pattern
  • Consensus (Paxos / Raft)
  • Transactions & ACID
  • Write-Ahead Log (WAL)
Used in

Foundational.

Lesson

How it works

Two-Phase Commit (2PC) is the protocol for atomic commitment across multiple participants. The setup: one coordinator (often the transaction manager) and N participants (databases, message brokers). The protocol has two phases:

Phase 1: Prepare (Voting)

  1. Coordinator sends PREPARE to every participant.
  2. Each participant writes the work to a stable log (WAL), so it can survive a crash. If the work can be committed, the participant replies VOTE YES. If any constraint fails, it replies VOTE NO and aborts locally.
  3. Coordinator collects all votes.

Phase 2: Commit or Abort (Decision)

  1. If all votes were YES: coordinator sends COMMIT to every participant.
  2. If any vote was NO (or any participant timed out): coordinator sends ABORT.
  3. Each participant applies the decision, releases locks, and sends ACK.
  4. Once the coordinator has all acks, the transaction is complete.

The key invariant: once a participant votes YES, it must be able to commit — it has locked its resources and must hold them until the coordinator's decision arrives. If the coordinator crashes, the participant is stuck.

The defining weakness of 2PC is blocking on coordinator failure. If the coordinator crashes after some participants have voted YES but before sending the COMMIT decision, those participants are stuck:

  • They cannot unilaterally commit — maybe the coordinator decided to abort.
  • They cannot unilaterally abort — maybe the coordinator decided to commit.
  • They hold locks on their resources, blocking other transactions.

The participants must wait for the coordinator to recover and read its decision log. This could be seconds (coordinator restarts) or hours (if the coordinator's disk is corrupted and a backup must be restored). During the entire wait, the locked resources are unavailable.

This is the fundamental trade-off: 2PC guarantees atomicity but sacrifices availability on coordinator failure. For tightly coupled databases in a single datacenter, this is acceptable — failures are rare and recovery is fast. For loosely coupled microservices spanning regions, it is unacceptable, which is why sagas have displaced 2PC in modern architectures.

Recovery protocols exist (presumed-abort, presumed-commit, two-phase with backup coordinators) but they reduce — not eliminate — the blocking.

Presumed Abort optimization

If the coordinator crashes before sending PREPARE, or if any participant votes NO, the transaction is aborted. The standard optimization 'presumed abort' lets participants autonomously abort if they have not heard from the coordinator within a timeout, without breaking atomicity — because a coordinator that never sent PREPARE could only have decided to abort. The reverse 'presumed commit' is unsafe: a coordinator that sent PREPARE but crashed before COMMIT cannot be presumed to have committed, so participants must block. This asymmetry is why 2PC remains blocking.

Three-Phase Commit (3PC) adds a third phase — PRE-COMMIT — between prepare and commit. The idea: after every participant has voted YES, the coordinator sends PRE-COMMIT (telling participants 'we will commit, get ready'); participants ack; only then does the coordinator send the final COMMIT. The benefit: if the coordinator crashes, participants can use the PRE-COMMIT acks to decide among themselves — if everyone received PRE-COMMIT, they can commit; if not, they abort. No blocking.

The catch: 3PC assumes synchronous networking (bounded message delay). On a real network with unbounded delays, 3PC can still block or, worse, allow inconsistent decisions (a partitioned participant believes it should commit while others abort). The FLP impossibility result proves that no asynchronous protocol can guarantee both safety and liveness — 3PC sacrifices safety under partition, which is worse than 2PC's blocking. So 3PC is rarely used in practice.

Modern distributed databases (Spanner, CockroachDB) use 2PC with Paxos/Raft-based coordinators — the coordinator itself is replicated, so coordinator failure means leader election within the coordinator cluster, not a single point of failure. This effectively gets the benefits of 3PC without its safety problems.

When is 2PC the right choice?

  • Internal coordination in a distributed database: when the participants are tightly coupled replicas or shards of the same logical database, owned by one team, in one datacenter. Spanner, CockroachDB, VoltDB use 2PC internally.
  • XA transactions across a database and a message broker in the same machine room: legacy J2EE/JTA setups. Works but slow.
  • Cross-shard writes in a sharded database: when a single transaction must update two shards. Rare and usually avoided by reshaping the schema.

When NOT to use 2PC:

  • Across microservices: the participants are independent services owned by different teams, possibly in different regions. Coordinator blocking is unacceptable; use sagas.
  • For high-throughput workloads: 2PC's latency (2-3 round trips plus fsync per participant) is too high.
  • When availability matters more than atomicity: 2PC is CP; use AP patterns (sagas, eventual consistency) instead.

The rule of thumb: 2PC inside a database, sagas between services. The CAP theorem decides: pick C (2PC) only when the participants are tightly coupled enough that you can tolerate blocking.

Check yourself
interview

The coordinator crashes after sending PREPARE to participants A and B, who both voted YES, but before sending COMMIT or ABORT. What happens?

Pick one answer.

Check yourself
expert

Why does Three-Phase Commit (3PC) attempt to solve the blocking problem, and why is it rarely used in practice?

Pick one answer.

Check yourself
solid

Your team is building a microservice that needs to atomically update two databases in different services. Why is 2PC a poor choice, and what should you use instead?

Pick one answer.

Engineering mental model

Mental model. Think of Two-Phase Commit as a deliberate boundary in a system. The boundary exists because something becomes harder to manage when everything is done in one place: latency, scale, failure isolation, consistency, cost, or team ownership. The useful question is not “what does Two-Phase Commit mean?” but “what pressure makes this boundary worth introducing, and what new failure mode does it create?”

Design lens

Before choosing Two-Phase Commit, name the workload, the critical user path, the dominant bottleneck, the failure you are trying to absorb, and the trade-off you are willing to accept. If you cannot name those five things, the design is probably premature.

Original NO CAP systems visual for Two-Phase Commit.
Image unavailable. Original NO CAP systems visual for Two-Phase Commit.
Two-Phase Commit: a compact system-thinking visual.— Original NO CAP visual.
// Pseudocode
request = receive()
result = two_phase_commit(request)
return result

// Production questions:
// 1. What happens on timeout?
// 2. Can this operation be retried safely?
// 3. What is the bottleneck?
A minimal engineering sketch for reasoning about Two-Phase Commit.

Back-of-the-envelope reasoning

Numerical lens: write down traffic, payload size, read/write ratio, peak multiplier and durability target before choosing a component. The numbers should justify the architecture.

Interactive sandboxdeterministic

Interactive thought experiment: Two-Phase Commit

Change the variables below and predict what breaks first in Two-Phase Commit. The production lab can later reuse these same inputs.

System pressure6%
Try this

Change one variable at a time. Predict the failure mode first, then move the slider and see whether your mental model matches the simplified system response.

Hint

If you are stuck on Two-Phase Commit, start by drawing the request path and marking every network hop, stateful component, queue, cache and failure boundary. Then estimate where the system will saturate.

Check yourself
solid

You increase traffic by 10× in a system using Two-Phase Commit. What should you inspect first?

Pick one answer.

Check yourself
interview

Which statement is the safest engineering habit when using Two-Phase Commit?

Pick one answer.

Try this
interview

You have dashboards for traffic, latency, errors and saturation. You can change the architecture, but every change has operational cost.

Production scenario: your system uses Two-Phase Commit, traffic suddenly spikes, and p99 latency doubles. What is your first move?

Interview drill

Answer this without notes: When would you choose Two-Phase Commit, and when would you intentionally avoid it? Mention at least one bottleneck it addresses, one failure mode it introduces, and one alternative. Then quantify the workload you are designing for.

Engineering lens

A useful engineering lens for Two-Phase Commit: define the problem it solves, the simpler design that fails first, the constraint that forces you to introduce this concept, and the new failure modes the concept creates.

Numerical sanity check

Back-of-the-envelope reasoning beats fake precision. State your traffic, payload, concurrency and growth assumptions explicitly, then calculate enough to know whether the current architecture is orders of magnitude away from the target.

Check yourself
interview

Do not optimize for a memorized definition. Reason from the workload and failure mode.

Imagine the simplest version of a system using Two-Phase Commit. What breaks first as traffic grows by 10×, and what would you change before reaching 100×?

Pick one answer.

Trade-offs

What you gain, what you pay

Pros
  • +Provides true atomicity across multiple resources — all commit or all abort.
  • +Well-defined protocol with decades of production experience.
  • +Used internally by distributed databases (Spanner, CockroachDB) with consensus-replicated coordinators.
  • +Preserves ACID guarantees across resources.
Cons
  • −Blocking on coordinator failure — locks held indefinitely during recovery.
  • −Slow — multiple round trips plus fsync on every participant per transaction.
  • −Single point of failure unless the coordinator is itself replicated (which adds complexity).
  • −Unsuitable for microservices — too tightly coupled, too slow, too brittle across regions.
  • −Recovery protocols (presumed-abort, presumed-commit) reduce but do not eliminate blocking.
Failure modes

How this breaks in production

  • Coordinator crash mid-protocol — participants block holding locks; mitigated by recovery logs but blocking is unavoidable.
  • Participant crash mid-protocol — recovery requires querying coordinator or other participants for the decision.
  • Network partition — participants cannot reach coordinator; they block.
  • Coordinator log loss — coordinator cannot recover its decision; participants must use heuristics (heuristic commit / heuristic abort), which can produce inconsistency.
  • Slow participant — coordinator and other participants wait; mitigated by timeouts but may abort unnecessarily.
Common mistakes

Don't fall into these traps

  • •Using 2PC across microservices — blocking is unacceptable; use sagas.
  • •Assuming the coordinator is highly available by default — it is a single point of failure unless replicated.
  • •Forgetting that participants hold locks during prepare-commit — long transactions can block many other transactions.
  • •Confusing 2PC with consensus (Paxos/Raft) — they solve different problems (atomic commitment vs. agreement on a value).
  • •Treating 3PC as a strict improvement over 2PC — 3PC sacrifices safety under partition, which is usually worse.
  • •Not configuring participant timeouts correctly — too short causes false aborts; too long increases blocking.
Where you see it

Real systems using this

Internal coordination in distributed databases (Spanner, CockroachDB, VoltDB).XA transactions in legacy J2EE/JTA environments (rare in modern stacks).Cross-shard writes in sharded databases (rare; usually avoided by schema design).Database + message broker atomicity (rare in microservices; replaced by outbox pattern).
Teardowns

How real systems implement this

  • Google Spanner — Uses 2PC across participant groups for ACID distributed transactions. The coordinator is itself a Paxos-replicated group, so coordinator failure means leader election within the group — not a single point of failure. TrueTime bounds clock uncertainty to ~7ms for external consistency.
  • CockroachDB — Distributed SQL database using 2PC across replicas for ACID transactions. Each range is a Raft group; cross-range transactions use 2PC over the Raft leaders. Latency is the trade-off but correctness is preserved.
  • Java Transaction API (JTA) / XA — Legacy standard for distributed transactions across XA-compliant resources (databases, JMS brokers). Coordinator (Transaction Manager) orchestrates 2PC. Slow, blocking, and largely avoided in modern microservices in favor of sagas.
  • PostgreSQL two-phase commit (PREPARE TRANSACTION) — Postgres supports the SQL commands PREPARE TRANSACTION and COMMIT PREPARED for use by external coordinators (like pgloader or a custom transaction manager). Used for cross-database transactions within a single cluster; rarely across services.
Interview prompts

Practice saying it out loud

  • Q1Walk through the two phases of 2PC. What can go wrong in each?
  • Q2Why does 2PC block on coordinator failure? Can you fix it?
  • Q3What is Three-Phase Commit, and why is it rarely used in practice?
  • Q4Why has the microservices community abandoned 2PC in favor of sagas?
  • Q5How do Spanner and CockroachDB use 2PC safely when the protocol is 'blocking'?
Research

Further reading & references

System Design Primer
Open source
ByteByteGo — Scale from zero to millions
ByteByteGo
System Design Tutorial
GeeksforGeeks
System Design Roadmap
roadmap.sh
Distributed Systems reference
Reference
Distributed Systems reference
Reference
Distributed Systems reference
Reference

Core explanations are original NO CAP material. External references are provided for deeper study and standards.

What next?

Mark as understood once the mental model clicks.

Next recommended

Saga Pattern