The Real Cost of Distributed Transactions: Why Two-Phase Commit Falls Apart at Scale
2PC promises the same all-or-nothing guarantee you get from a single database. At real scale, that promise comes with a bill most teams never budget for.
In a single database, a transaction is a simple deal: every operation commits together, or none of them do. Once that same guarantee has to span multiple machines, the deal gets a lot harder to keep, and two-phase commit was built specifically to keep it anyway. It works. It’s also the reason so many teams eventually walk away from distributed transactions entirely, in favor of something looser but far more forgiving: the saga pattern.
How Two-Phase Commit Actually Works
2PC uses a coordinator and a set of participants, and it runs in exactly two rounds, as the name suggests. In the first round, the voting phase, the coordinator asks every participant “can you commit?” and each one locks its resources and responds yes or no. In the second round, the coordinator tells everyone the final verdict: commit if every participant voted yes, abort if even one voted no.
This gives you real atomicity across machines. The catch is that every participant who votes yes has to hold its locks and wait, uncertain of the outcome, until the coordinator’s final word arrives. That waiting period is where the entire protocol’s reputation problem begins.
The Blocking Problem
Here is the failure mode that defines 2PC’s real-world cost. Once a participant has voted yes, it enters an uncertain state: committed to going along with whatever the coordinator eventually decides, but with no way to decide on its own. If the coordinator crashes after collecting votes but before broadcasting the final decision, every participant that voted yes is stuck holding its locks indefinitely, since it cannot safely commit or abort without knowing what the coordinator decided.
This is formally known as the blocking problem, and it isn’t a rare edge case, it’s a structural property of the protocol. A network partition that separates participants from the coordinator produces the exact same effect as a coordinator crash: participants who already voted yes are left holding locks with no way to reach a decision until connectivity, or the coordinator itself, comes back.

Why Adding a Third Phase Doesn’t Fully Solve It
The classic academic answer to blocking is three-phase commit, which adds a pre-commit round so participants have enough information to reach a decision even if the coordinator disappears mid-protocol. It genuinely reduces blocking, but it does so by adding an entire extra round trip to every single transaction, and it still cannot dependably recover from certain combined coordinator-and-participant failures. In practice, very few systems adopted 3PC widely, because paying for an extra network round trip on every transaction is its own tax, just a different one than blocking.
The Cost Nobody Puts on a Slide
Beyond the failure-mode risk, 2PC has an unavoidable latency cost built into its happy path. Every transaction requires two full network round trips between the coordinator and every participant, and the whole operation only completes as fast as the slowest participant responds. Spread those participants across regions or data centers, and that latency compounds directly into user-facing response time, on every single transaction, not just the failure cases.

There’s also a throughput cost that compounds quietly. Because participants hold locks for the entire duration of the protocol, high-throughput systems that need many transactions per second run directly into lock contention, since concurrent transactions touching overlapping data have to wait behind each other’s held locks. This is exactly why 2PC becomes harder to justify as transaction volume and participant count both grow, it isn’t one bottleneck, it’s several compounding at once.
The Saga Pattern: Trading Atomicity for Availability
The saga pattern takes a fundamentally different approach. Instead of one large distributed transaction, a saga breaks the operation into a sequence of independent local transactions, each one committing on its own, with no global lock and no single coordinator that can block the entire chain. If a step later in the sequence fails, the saga runs compensating transactions to undo the effects of the steps that already succeeded.
The trade-off is explicit and has to be accepted up front: the system is temporarily inconsistent while a saga is mid-flight. There’s a real window where some steps have committed and others haven’t yet, and any code reading that data during the window needs to be able to tolerate seeing a partial state. Sagas don’t hide that inconsistency, they just make it temporary and recoverable instead of pretending it can’t happen.
| Property | Two-Phase Commit | Saga Pattern |
|---|---|---|
| Consistency Model | Strong, atomic across all participants | Eventual, with a temporary inconsistent window |
| Failure Behavior | Can block indefinitely on coordinator failure | Rolls forward via compensating transactions |
| Locking | Holds locks across all participants until commit | No global locks; each local transaction commits independently |
| Failure Recovery Complexity | Simple in concept, painful in practice (blocking) | Requires designing compensating logic for every step |
| Best Fit | Small, tightly coupled systems where availability can be sacrificed | Independently deployed services where availability matters more than atomicity |
Neither Choice Is Free
This is exactly the CAP trade-off showing up again, in a different costume. 2PC prioritizes consistency hard, to the point of choosing to block rather than proceed with any uncertainty, which is a direct trade of availability for correctness. Sagas make the opposite bet: every local step keeps moving forward independently, and correctness across the whole operation is restored afterward, through compensation, rather than guaranteed upfront.
Neither one is the objectively correct choice. A payment system moving money between two accounts inside the same institution might reasonably accept 2PC’s blocking risk in exchange for a hard atomicity guarantee. An order-processing flow spanning inventory, payment, and shipping services, each independently deployed and independently scaled, is usually a much better fit for a saga, precisely because no single service should be able to freeze the other two just by going down at the wrong moment.
Quick Checklist: Choosing Between 2PC and a Saga
- Ask whether your services can tolerate holding locks across a network round trip; if not, 2PC’s cost is probably too high.
- Check how many participants are realistically involved, since 2PC’s coordination overhead scales with that count.
- If you choose a saga, design the compensating transaction for every step before you design the happy path.
- Decide explicitly whether a temporary inconsistent window is acceptable for your domain, rather than discovering it in production.
- Remember that 2PC’s blocking risk is a structural property of the protocol, not a rare implementation bug you can patch away.
What We Learned
Two-phase commit isn’t broken, it does exactly what it was designed to do: guarantee atomicity across multiple machines. The cost of that guarantee is a structural blocking risk whenever the coordinator fails at the wrong moment, plus a latency and lock-contention bill that grows with every participant and every network hop. The saga pattern doesn’t remove that cost, it relocates it, trading a hard consistency guarantee for availability and accepting a temporary inconsistent window in exchange. Neither approach is free, and the honest engineering question was never “which one is better,” it’s which cost your system, and your users, can actually afford to pay.



