Two-Phase Commit (2PC)
The classical, textbook answer to Chapter 15's problem. Every interview expects you to know it — and to know exactly why it's rarely the answer chosen in practice at real scale.
Two-Phase Commit introduces a coordinator that orchestrates every participant database through exactly two rounds:
Phase 1 asks every participant to prepare and vote; only if every single vote is "yes" does phase 2 tell them to actually commit. Any "no" (or timeout) in phase 1 sends ABORT to everyone instead.
| Phase | What happens |
|---|---|
| 1 — Prepare (voting) | The coordinator asks every participant "can you commit this?" Each participant does everything short of actually committing — locking rows, validating constraints — and replies YES or NO, without yet making anything permanent. |
| 2 — Commit/Abort | If every participant voted YES, the coordinator tells all of them to actually commit. If any participant voted NO (or didn't respond in time), the coordinator tells all of them to abort instead. |
coordinator.beginGlobalTransaction(); // PHASE 1: prepare (each participant locks and validates, but does not commit) boolean orderReady = orderService.prepare(orderData); // locks the row, checks constraints boolean paymentReady = paymentService.prepare(paymentData); // pre-authorizes the card, doesn't capture yet boolean stockReady = inventoryService.prepare(items); // reserves stock, doesn't finalize yet // PHASE 2: decide if (orderReady && paymentReady && stockReady) { orderService.commit(); paymentService.commit(); inventoryService.commit(); } else { orderService.abort(); paymentService.abort(); inventoryService.abort(); }
💻 Code example
coordinator.beginGlobalTransaction(); // PHASE 1: prepare (each participant locks and validates, but does not commit) boolean orderReady = orderService.prepare(orderData); // locks the row, checks constraints boolean paymentReady = paymentService.prepare(paymentData); // pre-authorizes the card, doesn't capture yet boolean stockReady = inventoryService.prepare(items); // reserves stock, doesn't finalize yet // PHASE 2: decide if (orderReady && paymentReady && stockReady) { orderService.commit(); paymentService.commit(); inventoryService.commit(); } else { orderService.abort(); paymentService.abort(); inventoryService.abort(); }
▲ Why 2PC is rarely chosen at real scale — the core reasons
Blocking: every participant that votes YES in phase 1 must hold its locks and wait for the coordinator's phase 2 decision — if the coordinator itself crashes after collecting votes but before sending the final decision, every participant is stuck holding locks indefinitely, unable to independently decide whether to commit or abort. Single point of failure: the coordinator is a new, critical piece of infrastructure that didn't exist before — its availability now gates every distributed transaction across every participating service. Poor horizontal scaling: every participant must stay synchronously available and responsive throughout both phases, directly working against microservices' usual goal of independent availability and scaling per service.
| Context | Why 2PC still fits |
|---|---|
| Within a single database engine, across multiple resources (e.g. XA transactions across two tables in different tablespaces) | Low latency, tightly-controlled environment — the blocking risk is much smaller and better managed. |
| Some traditional enterprise systems (e.g. certain banking core systems) with strict consistency mandates | Regulatory/correctness requirements sometimes outweigh the availability cost, in controlled, lower-throughput contexts. |
| Modern high-scale microservices (the mainstream case) | Generally avoided — Saga (Chapter 17) is preferred specifically because it doesn't require synchronous, blocking coordination across independently-scaled services. |
Want a visual for this concept?
Generate a diagram tailored to “Two-Phase Commit (2PC)” — the AI picks whichever visual (flowchart, comparison, sequence, etc.) best fits.
Sign in to generate a visual →