Distributed transactions
One database promised you atomicity; now the write spans three systems that each promise nothing about the others.
System Design’s writes chapter ended at a cliff edge: one database’s transaction protects one database, and the interesting workflows span three. This chapter is the cliff itself — what atomic commitment across systems really costs, the two serious ways to pay, and the honest alternative most products should choose.
Two-phase commit, properly
The protocol is the obvious one, done carefully. Prepare: the coordinator asks every participant to get the transaction durable-but-uncommitted and vote; a yes vote is a binding promise — the participant logs it and holds its locks. Commit: unanimous yes → coordinator logs the decision (this log write is the commit) and broadcasts it. The flaw is structural, not an implementation bug: between voting yes and hearing the outcome, a participant is in doubt — it cannot commit, cannot abort, cannot release locks. If the coordinator dies there, participants block, locks pile up, and throughput collapses outward. 2PC converts one node’s failure into everyone’s wait, which is exactly the disease this pathway keeps diagnosing.
Making it survivable — the modern moves
The blocking window is a coordinator-availability problem, so both serious fixes attack that. Spanner’s move: make every participant and the coordinator a Raft/Paxos group rather than a machine — the coordinator cannot “die” short of losing a majority, so 2PC-over-consensus keeps atomicity and sheds the classic blocking. Percolator’s move (this chapter’s paper): no coordinator process at all. Writes go in as locks against a primary row; commit is a single atomic write that flips the primary’s lock to a commit record; every other row’s fate is derived lazily by readers who chase the primary. The commit point moved into the data, so any survivor can finish or roll back an orphaned transaction. Two different budgets, one shared lesson: atomic commitment is exactly as available as its commit record.
The honest alternative, and how to choose
Sagas — local transactions plus compensations, driven through the outbox machinery — trade isolation for availability and were covered on the System Design side. The choice between the worlds is not taste; it is one question: can the intermediate state be made honest? “Payment pending” is an honest state a user can see; “seat half-sold to two people” is not. Where every in-between state can be named and survived, take sagas and keep your availability. Where the invariant is truly atomic — ledger balancing, inventory that must never double-sell — pay for real atomic commitment over replicated participants, and keep the transaction as small and short as it can possibly be: fewer participants, fewer locks, less doubt.
Where it bites
- 2PC across services on the request path, coordinator on one box — the textbook outage, still regularly shipped.
- Sagas without designed compensations — an undo path invented during the incident is not an undo path.
- Mixing regimes accidentally: half the workflow atomic, half eventual, and no one able to state the invariant that survives.
- Forgetting the dedup window: “exactly-once” across systems is at-least-once plus idempotency keys plus a bounded memory of them — chapter one, forever.