A single database can promise a transaction is all-or-nothing. The moment that transaction has to touch two separate databases, keeping that same promise gets a lot harder — and the classic answer comes with a failure mode that's genuinely difficult to fully solve.
3 min read
A normal database transaction guarantees atomicity — every statement in it commits, or none do. But what if an operation needs to update two separate databases (or a database and a message queue) as a single logical unit — say, deducting inventory in one system and charging a payment in another? If the first succeeds and the second fails, the two systems are now inconsistent with each other, and neither one alone has enough information to know that.
A coordinator manages the transaction across multiple participants (the individual databases/systems involved), in two phases:
Phase 1 (Prepare): the coordinator asks every participant to do everything needed to commit except actually finalize it — locking the necessary rows, writing the change durably, and replying "yes, I can commit" or "no, abort." Phase 2 (Commit/Abort): only if every participant said yes does the coordinator tell everyone to actually commit; if any participant said no (or timed out), it tells everyone to abort instead.
If the coordinator crashes after participants have all replied "yes" in phase 1 but before sending the final commit/abort decision, every participant is stuck: it has already promised to commit (its data is locked, waiting) and cannot unilaterally decide to abort or commit on its own, because it doesn't know what the other participants were told. This is called the blocking problem, and it's the central, structural weakness of 2PC — participants can be left holding locks indefinitely until the coordinator recovers (or a human intervenes), during which time that data is effectively unavailable to anyone else.
Beyond the blocking problem, 2PC requires every participant to be available and responsive for the entire duration of the transaction — directly conflicting with the AP (availability-favoring) philosophy that many large-scale distributed systems deliberately choose, per System Design's CAP theorem lesson. A single slow or unreachable participant blocks the whole transaction, and the failure mode when the coordinator itself dies is severe enough that most large-scale systems avoid 2PC for anything beyond a small, tightly-controlled set of critical systems (2PC is still genuinely used in some traditional enterprise database and message broker integrations, where the trade-off is acceptable).
3PC adds an extra phase specifically to reduce (not eliminate) the blocking problem, by giving participants enough extra information to make a safe decision without the coordinator in more scenarios. In practice, 3PC adds real complexity and still doesn't fully solve blocking under all network-partition scenarios, which is a large part of why it's rarely deployed — most systems that need to avoid 2PC's blocking problem reach for a fundamentally different approach instead: the Saga pattern, covered next.
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What does Two-Phase Commit's Phase 1 (Prepare) actually ask participants to do?
2. What is the 'blocking problem' in Two-Phase Commit?
3. Why have many large-scale distributed systems moved away from relying on 2PC?
Distributed Systems Fundamentals