Sagas and compensating actions
Design recoverable workflows whose committed steps can interleave, without describing semantic compensation as isolated rollback.
On this page
The question for today
What you’ll understand
- Distinguish semantic compensation from restoring an old database image.
- Track durable workflow intent and retry-safe step identities.
- Evaluate intermediate visibility, irreversible actions, and compensation failure.
Provisioning a training workspace may reserve quota, create storage, and configure an external service. Holding one distributed database transaction open across all those steps may be impractical or unsupported. A saga breaks the workflow into locally committed steps with defined recovery actions. The design accepts intermediate visibility and interleaving, so its compensations must respect other work rather than pretending that time can be rolled backward.
Committed steps remain visible
A saga step commits under its local transactional contract before later steps necessarily succeed. Other transactions may observe and act on that intermediate result. If the workflow later compensates, those observers do not automatically forget what they saw. Design the allowed intermediate states explicitly, such as reserved, provisioning, active, and compensating. A product promising immediate all-or-nothing visibility cannot obtain that promise merely by renaming a sequence of independent API calls a saga.
A compensation semantically amends a completed step. Releasing a reservation should remove that reservation’s contribution rather than restore the entire old capacity counter, because unrelated reservations may have happened meanwhile. Some effects are inherently irreversible: a sent notification cannot be unsent, and consumed compute cannot be unconsumed. Their recovery may be an additional message, credit, or operational process with a defined business meaning. Verify that these alternatives satisfy the real invariant instead of claiming they recreate the exact initial state.
Recovery needs durable progress and compatible code
Record workflow identity, completed step identities, arguments needed for compensation, and the chosen recovery direction durably enough for the failure model. A crash after a step commits but before its reply is recorded creates an ambiguous outcome, so the step needs idempotency or authoritative reconciliation. Retrying a compensation can face the same ambiguity. Preserve logical step identity across attempts and define how a duplicate is recognized. A queue delivering each message at least once does not automatically make each business action occur once.
Compensation can fail, take time, or become temporarily unavailable. Track unresolved obligations and retry them under a policy rather than marking the workflow fully reversed as soon as a request is sent. Reverse order is useful when steps depend on one another, but independent branches may permit other safe schedules. Application upgrades also matter: retained workflow records must remain interpretable by the code that finishes old compensations. Deleting a previous handler version can break recovery for workflows that were valid when started.
Choose invariants that survive interleaving
A useful saga invariant may concern ownership of reservations, eventual resolution of an intent, or a maximum unfulfilled exposure, rather than the false promise that no intermediate state exists. Test another workflow arriving between a forward step and its compensation. Decide whether partially provisioned resources should be hidden, leased, or usable, and account for expiry races. If a critical action cannot be compensated or safely retried, consider making it the final pivot or using a different protocol; there is no universal ordering that removes every business risk.
The Markov lab supplies a narrow resolution analogy. Reset, set transition p from state zero to one to 0.5 and reverse transition q to 0, predict the resolved-state probability after two transitions, then Step twice. Compare a fresh Reset with both p = 0.25 and q = 0, predicting before two Steps. The model starts entirely in state zero and makes state one absorbing when q is zero. It is an exact two-state probability model, not a saga executor, compensation guarantee, or claim that real recovery attempts are independent.
Work it through
Compensate one reservation without erasing another
An inventory starts with 10 available quota units. Saga A reserves 3. Before A compensates, independent saga B reserves 2 and remains valid. Assume additive atomic reservations and releases, no consumption, and no other changes.
A’s reservation leaves 10 − 3 = 7 available units. B’s later reservation leaves 7 − 2 = 5. Both reservations are now part of current state.
Compensating A releases only A’s 3 units: 5 + 3 = 8. B’s two-unit reservation remains honored.
Restoring A’s old observed value of 10 would erase B’s valid reservation. The resulting counter would overstate availability by two units.
The correct compensated balance is eight, not the original ten. Compensation preserves intervening work rather than restoring a historical image.
The living notebook
Explore the model
Interactive lab · markov
An absorbing resolution analogy
Reset, set transition p = 0.5 and reverse transition q = 0, predict P(state 1) after two transitions, then Step twice. Compare a fresh Reset with both p = 0.25 and q = 0 before two Steps. This exact chain is not a saga or compensation simulator.
- P(state 1) at step 0
- 0%
- Stationary P(state 1)
- 60%
- Chain behavior
- Monotone convergence
Exact finite-step probabilities for a two-state chain starting in state 0, not sampled trajectories. Playback advances two transitions per second. When p = q = 0 every distribution is stationary; when p = q = 1 the chain alternates forever. A single zero transition creates an absorbing state. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.
Look forEven a useful probability analogy cannot establish semantic compensation, idempotency, or recovery-code durability.
Pause & reason
Check your understanding
Your turn
Try it yourself
Capacity begins at 20 units. A reserves 6, B then reserves 4, and A is compensated. Find final available capacity. Separately, in the lab’s q = 0 chain starting in state zero, calculate state-one probability after two transitions for p = 0.5 and for p = 0.25.
Take a moment to make a prediction or work it out on paper. Open a hint when you need one.
Give me a hint
Preserve B’s reservation. For the chain, the probability of staying in zero twice is (1 − p) squared.
Compare with the solution
Capacity evolves 20 → 14 → 10 → 16. Restoring 20 would incorrectly erase B’s four-unit claim. With p = 0.5, state-one probability is 1 − 0.5 squared = 0.75. With p = 0.25 it is 1 − 0.75 squared = 0.4375. These probabilities belong only to the specified two-state model, not an assurance that a real compensation succeeds within two attempts.
Keep the idea
Take it with you
- Sagas expose locally committed intermediate states and permit interleaving.
- Compensation amends owned effects without erasing unrelated later work.
- Durable workflow progress, retry-safe identities, and unresolved-recovery handling are essential.
Read further
Original sources and deeper explanations to follow at your own pace.
Optional progress, saved in this browser when available. Revisit any lesson at any time.
Lesson not marked complete.