Consensus Algorithms · Phase 4

Consensus Basics

Article 4.4 of 5

Jul 12, 2026 · devops · 20 min read · 4200 words intermediate

Two-phase commit (2PC).

devops distributed-systems 2pc series-consensus

Two-Phase Commit (2PC), formalized in the 1970s, is the historical protocol that directly motivated much of the consensus research this series has been building toward — and it's the protocol every distributed-transactions system (spanning multiple independent databases, services, or shards) reaches for first, because the problem it solves is genuinely different from the SMR/atomic-broadcast problem of the last three articles. This article covers the protocol precisely: prepare and commit phases, the participant vote, and — the reason this article exists in a consensus series at all — the specific, well-known failure scenario where 2PC provably blocks (the whole system freezes, indefinitely, waiting for one crashed process) even though every other participant is alive and willing to proceed. Understanding exactly why 2PC blocks, and exactly what "consensus" adds that 2PC lacks, is the cleanest possible motivation for why the classical algorithms in Phase 5 exist at all.

The problem 2PC solves: atomic distributed transactions

2PC solves a different problem from the SMR/replication machinery of Phase 3 — worth being precise about the distinction upfront. SMR and atomic broadcast (articles 3.1, 4.3) are about getting multiple replicas holding the same data to agree on a sequence of operations. 2PC is about getting multiple different, independent systems (which might hold entirely different data — a payments database, an inventory service, a shipping system) to all agree on whether a single transaction spanning all of them should be committed or aborted, atomically — either every participant applies its part of the transaction, or none do. The canonical example: transferring money between accounts held in two different bank databases — debit account A in database 1, credit account B in database 2, and both must happen, or neither.

The protocol, phase by phase

2PC involves one coordinator (orchestrating the transaction) and multiple participants (each responsible for one piece of the transaction):

Phase 1: Prepare (voting phase)

The coordinator sends a PREPARE message to every participant, describing the transaction. Each participant does whatever local work is needed to be certain it can commit if asked — validating constraints, acquiring locks, writing the change to its own durable log (but not yet actually applying/committing it) — then replies either VOTE-COMMIT (I can definitely do this) or VOTE-ABORT (I can't — a constraint failed, or I hit an error).

Phase 2: Commit or Abort (decision phase)

If the coordinator receives VOTE-COMMIT from every participant, it durably records the decision "commit" and sends a COMMIT message to everyone, who then actually apply the change and release any locks. If any participant voted VOTE-ABORT (or failed to respond within a timeout), the coordinator sends ABORT to everyone, and all participants discard their prepared changes and release locks.

Coordinator                Participant A          Participant B
     |-------- PREPARE --------->|                      |
     |-------- PREPARE ------------------------------->  |
     |<------- VOTE-COMMIT ------|                      |
     |<------- VOTE-COMMIT ------------------------------|
     |  (all voted commit -- durably record "COMMIT")    |
     |-------- COMMIT ---------->|                      |
     |-------- COMMIT ----------------------------------->|
     |<------- ACK ---------------|                      |
     |<------- ACK ----------------------------------------|

Notice the crucial detail in Phase 1: a participant that votes VOTE-COMMIT has made a binding promise — it must be able to commit later, no matter what, if the coordinator says so. This is exactly why the participant must durably persist its prepared state before voting commit: it might crash and restart, and upon restart, must still be able to honor a commit decision that arrives for a transaction it already voted commit on. This durability requirement is the direct seed of the blocking problem.

The blocking problem: 2PC's fatal flaw

Here's the exact scenario, and it's worth walking through slowly because it's the single most important thing to understand about 2PC: the coordinator collects all votes, every participant voted commit, the coordinator durably decides "commit" — and then the coordinator crashes, before sending the COMMIT message to anyone.

Every participant is now stuck in a genuinely unresolvable state, called uncertain or in-doubt: it voted commit (a binding promise), it's holding locks and prepared changes, and it has no way to know whether the coordinator's actual final decision (which it never received) was commit or abort. Critically — and this is the crux of the whole problem — the participants cannot resolve this among themselves. They can talk to each other, but none of them knows what the coordinator decided, because only the coordinator ever durably recorded the final decision. The participant must simply wait — holding its locks, unable to proceed, unable to safely guess — until the coordinator recovers and tells it the real answer.

2PC's blocking problem: coordinator crashes after deciding, before announcing Coordinator decided COMMIT, then CRASHED Participant A STUCK: voted commit, holding locks, must wait Participant B STUCK: voted commit, holding locks, must wait A and B are both alive, both willing — but CANNOT proceed without the coordinator. This is blocking.

Fig 1 — Every participant is alive and cooperative, yet the entire transaction is frozen until the single crashed coordinator recovers.

This is exactly the FLP-adjacent, safety-vs-liveness distinction this phase has been building — and it's worth being precise about which one 2PC's blocking actually is. Blocking is a liveness failure (article 4.1), not a safety one — 2PC never commits a transaction on some participants while aborting it on others; it just sometimes gets stuck, unable to make progress at all, for an unbounded amount of time. That's genuinely bad — a stuck transaction holds locks, which can cascade into blocking other, unrelated transactions waiting on those same locks — but it's a fundamentally different, more contained failure mode than a safety violation would be.

Precisely why 2PC is not consensus

This is the question this article exists to answer clearly, and now the machinery from Phases 1-4 makes the answer precise rather than hand-wavy: 2PC has no majority-quorum mechanism at all. The commit-or-abort decision depends on every single participant agreeing, and — critically — the coordinator's own durability is a single point of failure with no redundancy. Compare directly against article 3.3's core proof: consensus algorithms achieve liveness despite individual node failures precisely because they only need a majority, and any majority overlaps with any other, so no single node's failure can ever leave the system unable to determine the right answer. 2PC has nothing resembling this — its "coordinator" is a single point of failure for the decision itself, not merely for availability, which is a fundamentally weaker design than anything from Phase 3 onward.

Property2PCConsensus (Paxos/Raft, Phase 5)
Agreement requirementALL participantsMAJORITY of nodes
Coordinator/leader failureCan permanently block the whole transactionTriggers a new election (article 4.2); system continues
Decision durabilityOnly on the coordinator (single point of failure)Replicated across a majority (article 3.2/3.3)
Liveness guaranteeNone under coordinator failure — can block indefinitelyGuaranteed under partial synchrony (article 2.5's escape hatch)

Put simply: 2PC solves a related but easier-sounding, and in an important sense actually harder-in-practice, problem — atomic cross-system transactions — using a mechanism (a single coordinator, unanimous agreement) that has none of the fault-tolerance machinery Phase 3 spent so much effort building. This is precisely why 2PC is still widely used for genuinely cross-system distributed transactions (where you can't avoid coordinating truly independent systems) while being entirely unsuitable as the replication mechanism for a single fault-tolerant service — that job belongs to the majority-quorum-based algorithms in Phase 5, which is exactly the historical motivation: 2PC's well-documented blocking problem is a direct part of what pushed distributed-systems research toward majority-based consensus in the first place.

FAQ

Can 2PC's blocking problem be mitigated with timeouts?

Only partially, and with real risk — a participant could, after a long timeout, decide to unilaterally abort rather than wait forever. But this is unsafe in general: if the coordinator's actual (lost) decision was commit, a participant that unilaterally aborts has now created exactly the safety violation 2PC was designed to prevent (some participants committed, others aborted, for the same transaction). This is precisely the gap Three-Phase Commit (article 4.5, next) is specifically designed to close.

Is 2PC ever combined with consensus in practice?

Yes, commonly — a well-engineered production system often makes the "coordinator" itself a consensus-backed, replicated role (rather than a single physical machine), so that if the coordinator process dies, a majority-elected replacement can take over and recover the in-doubt decision from a replicated log, rather than the whole transaction blocking on one dead machine. This hybrid pattern — 2PC's cross-participant protocol layered on top of a consensus-backed coordinator — shows up in several production systems covered in Phase 9.

What happens to the locks held by an in-doubt participant, practically?

They're held for as long as the participant remains uncertain — which, in the worst case demonstrated above, is unbounded. This is a real, serious operational concern in production 2PC deployments: a stuck coordinator doesn't just block one transaction, it can cascade into blocking every other transaction competing for the same locked resources, which is why 2PC coordinators are typically given aggressive monitoring and fast recovery/failover procedures in practice.

Does 2PC guarantee atomicity even when it blocks?

Yes — this is the precise, important distinction this article has been building toward. Blocking is 2PC failing at liveness (it can't make progress), but it never fails at safety (atomicity) — a blocked transaction is never partially committed; it's simply stuck, undecided, for both possible outcomes, until the coordinator's actual decision becomes known.

Takeaways

  • 2PC solves atomic distributed transactions across independent systems — a different problem from Phase 3's replicated-log/SMR machinery, even though both involve "agreement."
  • The protocol: Prepare (coordinator collects binding votes) then Commit/Abort (coordinator's durable decision, propagated to all participants).
  • The fatal blocking problem: if the coordinator crashes after deciding but before announcing, every participant is stuck indefinitely — unable to resolve the uncertainty among themselves, no matter how many of them are alive and willing.
  • Blocking is a liveness failure, not a safety one (article 4.1) — 2PC never partially commits a transaction, it just sometimes can't make progress.
  • The root cause, made precise by everything Phases 1-4 built: 2PC requires unanimous agreement and has a single point of failure for the decision itself — it has none of the majority-quorum fault tolerance (article 3.3) that makes consensus algorithms resilient to individual node failures.
  • This well-documented failure mode is a direct, historical part of what motivated majority-based consensus research — the subject of Phase 5, starting next article with 3PC's partial (but ultimately incomplete) fix.

References & further reading

← 4.3 Atomic Broadcast next: 4.5 Three-Phase Commit (3PC) →
© cvam — written in plaintext, served warm