Distributed Transactions & Sagas
7 lessons. Every one names the guarantee it claims, what a node can know, and how it fails.
One database gives you all-or-nothing for free. Two services do not, because there is no component that can see both uncommitted states at once. Before reaching for a protocol, look hard at whether the invariant has to span the boundary at all.
Q · Two services must both change state, or neither. Neither can see the other’s uncommitted work. What are my actual options?
2PC makes several independent stores commit or abort together. It works by making each participant give up its right to decide alone — and that surrendered right is both the source of the guarantee and the source of every problem the protocol has.
Q · How can three independent databases be made to commit or abort as one, and what exactly does that cost?
The real objection to two-phase commit is one specific gap: the coordinator dies after collecting YES votes, and every participant sits holding locks with no legal way to decide. Understanding that window precisely tells you both why 2PC gets a bad name and how modern systems remove the problem.
Q · The coordinator crashed after everyone voted yes. Why can the participants not simply decide for themselves?
A saga replaces one atomic transaction with a sequence of local ones, each committed immediately and each paired with a compensating action. It never blocks. In exchange it gives up the "I" in ACID entirely — the half-finished state is visible to everyone, and your business logic now has to cope with it.
Q · If I cannot hold a transaction open across services, what does the alternative actually guarantee — and what does it stop guaranteeing?
A rollback erases history: the old value returns and nobody can prove the new one ever existed. A compensation adds history: the money moved, then moved back, and the statement shows both lines. Time did not reverse. Everything hard about sagas follows from that one difference.
Q · My saga failed at step 3, so I will undo steps 1 and 2. Why is "undo" the wrong word, and what does it cost me to use it?
An orchestrator holds the saga as an explicit state machine, issues commands and records replies. You can point at one place and ask "where is order 4821?" — and you have created a component that every workflow now depends on, and whose deployments must cope with thousands of in-flight sagas running the previous version.
Q · Who owns the knowledge of what step a saga is on — and what changes when the answer is "one component"?
Each service reacts to events and publishes its own. No component owns the flow, so adding a participant requires changing nothing upstream. The cost is that the workflow exists only as an emergent property of a subscription graph — and nobody can read it, test it end to end, or say where a given order is.
Q · If services just react to each other’s events, what happens to the workflow — and to my ability to reason about it?