Introduction

Imagine moving money between two banks, or booking a flight and a hotel in one click. The change has to happen everywhere or nowhere — half a transfer, with money leaving one account but never arriving in the other, is a disaster. Computer scientists call this property atomicity: all of it commits, or all of it is undone.

That is easy on a single machine. But when the data lives on several machines that can crash or lose messages at any moment, getting them to agree becomes surprisingly delicate. How do you make independent computers either all say "yes, it's done" or all roll back together?

The classic answer, designed in the 1970s and still running inside databases today, is the two-phase commit protocol (2PC): first everyone votes, then everyone acts on the unanimous result.

Run the Protocol

Below is a coordinator and three participants (think of them as three databases). Pick how each one will vote, then press Run protocol. In Phase 1 the coordinator asks everyone to prepare and collects votes. In Phase 2 it broadcasts the verdict.

<p class="hint">{{hint}}</p>
<div class="coord" id="coord">{{coord_idle}}</div>
<div class="parts" id="parts"></div>
<div class="status" id="status">{{status_initial}}</div>
<div class="btns">
  <button id="run" type="button">{{btn_run}}</button>
  <button id="reset" type="button" class="ghost">{{btn_reset}}</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
.coord { font: 700 14px system-ui, sans-serif; background: #1d3557; color: #fff;
         padding: .55rem .8rem; border-radius: 8px; text-align: center; margin-bottom: .7rem; }
.coord.commit { background: #0a7d33; }
.coord.abort { background: #c92f3c; }
.parts { display: grid; grid-template-columns: repeat(3, 1fr); gap: .6rem; }
.part { border: 1px solid #cdd9e3; border-radius: 10px; padding: .6rem; background: #f4f7fa; }
.part h4 { margin: 0 0 .4rem; font-size: .9rem; color: #1d3557; }
.vote { display: flex; gap: .3rem; margin-bottom: .5rem; }
.vote button { flex: 1; font: 600 12px system-ui; padding: .3rem; border: 1px solid #adb1b8;
               background: #fff; color: #444; border-radius: 6px; cursor: pointer; }
.vote button.sel-yes { background: #0a7d33; color: #fff; border-color: #0a7d33; }
.vote button.sel-no { background: #c92f3c; color: #fff; border-color: #c92f3c; }
.state { font: 700 13px ui-monospace, monospace; text-align: center; padding: .35rem;
         border-radius: 6px; background: #e8eef3; color: #1d3557; min-height: 1.6em; }
.state.committed { background: #d6f0de; color: #0a7d33; }
.state.aborted { background: #f7d9dc; color: #c92f3c; }
.status { font-size: 1rem; font-weight: 600; margin: .7rem 0 .5rem; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button.act { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
         background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
#run { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
       background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
#reset { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
         background: #fff; color: #1d3557; border-radius: 8px; cursor: pointer; }
// Code not found

The rule is brutally simple: the transaction commits only if every participant votes YES. Flip even one vote to NO and watch the coordinator order a global ABORT — every machine rolls back, including the ones that were perfectly happy to commit. That all-or-nothing guarantee is the whole point.

The Real Complexity

Two-phase commit works — it always preserves atomicity. So where is the catch?

  • Phase 1 (prepare/vote): the coordinator asks each participant "can you commit?" Each one locks the data, writes the change to a durable log, and answers YES or NO. A YES is a binding promise: I can no longer change my mind.
  • Phase 2 (commit/abort): if all votes were YES the coordinator decides COMMIT; if any was NO it decides ABORT, and tells everyone.
  • It is blocking. Suppose a participant has voted YES and then the coordinator crashes before announcing the verdict. That participant is stuck holding its locks, unable to commit or abort on its own — it must wait for the coordinator to recover. This is the famous weakness of 2PC.
  • And it is fundamental. The 1985 result of Fischer, Lynch and Paterson (FLP) proved that in an asynchronous network where even one machine may fail, no protocol can guarantee both correctness and always reaching a decision. 2PC chooses safety over liveness; refinements like three-phase commit and consensus algorithms (Paxos, Raft) reduce the blocking but never abolish the underlying limit.

So the status is precise: two-phase commit is a proven-correct atomic-commit protocol, but a provably blocking one — and the impossibility of escaping that entirely is the same kind of hard limit explored in P vs NP and the halting problem.

Where It Matters

"Make this change everywhere or nowhere" is one of the most demanded guarantees in real systems, and two-phase commit is its textbook implementation:

  • Distributed databases: when a single query touches data on several nodes, 2PC (often via the XA standard) keeps them consistent.
  • Bank and payment systems: debiting one account and crediting another must be atomic — exactly the all-or-nothing case.
  • Microservices: spreading one business action across many services revives the same problem; teams use 2PC, or looser saga patterns when blocking is unacceptable.
  • Message queues and brokers: committing a message and the work it triggered as one unit is an atomic-commit problem in disguise.

Understand two-phase commit and you have met distributed consensus — the same agreement problem behind replicated state machines, blockchains, and any system where independent computers must speak with one voice. Compare it with the search-and-verify gap in SAT.

Conclusion

Two-phase commit captures a deep idea in a tiny ritual: ask everyone first, then act on the unanimous answer. A single NO is enough to roll the whole world back, and that is exactly what keeps distributed data honest.

But the protocol also wears its limits on its sleeve. Pause it at the wrong instant — a coordinator down between vote and verdict — and machines sit waiting, unable to safely move. That blocking is not sloppy engineering; it is the shadow of the FLP impossibility, the unavoidable cost of demanding perfect agreement from machines that can fail. All-or-nothing, it turns out, is never entirely free.

Share this article

Pick a channel — or use your device's native share sheet.

Comments

Loading comments...

https://www.kipuhub.com/en/article/two-phase-commit/Content licensed under CC BY-NC 4.0.