Compensating Transactions
When a multi-step operation fails halfway and the finished steps cannot be rolled back, you undo them with new actions: refunds, cancellations, corrections. Learn how to design compensations that are safe to retry and honest about what cannot be undone.
In a nutshell
- A compensating transaction undoes the effect of a completed step with a new action, not by erasing history.
- Compensations run in reverse order and must be idempotent, since they may be retried.
- Some effects cannot be undone (an email was sent, goods were shipped): compensate with a business action (apology, return label).
- Record each step's progress durably, so compensation can resume after a crash.
Undo by doing something new
Inside one database, a rollback makes a failed transaction disappear. Across services, each step has already committed and others may have seen it. The only way back is forward: a refund for a charge, a cancellation for a booking, a credit note for an invoice. This is the undo side of a saga.
| Step | Compensation | Notes |
|---|---|---|
| Reserve stock | Release the reservation | Easy: an internal state change |
| Charge card | Refund | The customer may see both on their statement |
| Book hotel | Cancel booking | May cost a cancellation fee: a business decision |
| Send confirmation email | Send a correction email | Cannot be unsent |
| Ship goods | Arrange a return | Slow and expensive: put irreversible steps last |
Designing compensations
- Durable progress log: record which steps completed (in the orchestrator's database or as events), so a crashed coordinator can pick up and compensate.
- Idempotent: "refund payment 812" twice must refund once; use the original step's ID as the compensation's idempotency key.
- Retry until done: a compensation that fails must be retried (with backoff), and alert a person if it keeps failing.
- Semantic, not exact: the system returns to a consistent business state, not to the exact bytes it had before. Other changes may have happened in between.
- Visible states: mark records as "pending" or "cancelling" so users and other services understand what they see.
The code sample in the distributed transactions topic shows an orchestrator that runs steps and, on failure, runs compensations newest-first.
Common mistakes
- Compensations that are not idempotent, so a retry refunds twice.
- Running irreversible steps early in the flow.
- Keeping saga progress only in memory, losing it on a crash.
- Assuming compensation restores the exact previous state.
Interview questions
Try answering out loud before you open each one.
Payment succeeded but the shipment service rejects the order. What happens?
The orchestrator records the failure and runs compensations in reverse: release the stock reservation, refund the payment (idempotent, keyed by the payment ID), mark the order cancelled and notify the customer. Each compensation is retried until it succeeds, with alerts if it cannot.
Check yourself
Related topics
Further reading
- Compensating Transaction pattern Microsoft Azure Architecture Center
- Pattern: Saga microservices.io
Your notes
Saved in this browser as you type.
Finished this topic?
Mark it read to track your progress, then carry on with the next one.