Compensating Transaction
A compensating transaction undoes the effects of a previously committed transaction by executing a reverse operation — not by rolling back. It's the rollback mechanism of the Saga pattern: when a step in a multi-service workflow fails, you call compensations for each prior step in reverse order. Unlike a database rollback, compensations are business-level operations that work across services and don't require distributed locks.
Foundational.
How it works
A compensating transaction is the answer to: "I committed step 3, but step 5 failed — how do I undo step 3?" In a single ACID database, you'd ROLLBACK. But across services, each commit is final — the inventory service doesn't hold a lock waiting for the payment service to confirm. You can't roll back; you have to compensate.
Compensation means: execute a new operation that semantically undoes the original. If you charged a card, refund it. If you reserved inventory, release it. If you sent a confirmation email, send a cancellation. The original committed transaction stays committed; the compensation is a new, separate transaction.
This is the heart of the Saga pattern: a sequence of local transactions, each with a compensation. On any failure, run compensations in reverse order. The end state is consistent — not because of ACID, but because the compensations restored invariants.
Key insight: compensation is not rollback. A rollback restores the previous state as if the transaction never happened — only possible when locks are held. A compensation accepts that the transaction happened (other systems may have observed it) and adds a new transaction that semantically reverses it.
Practical consequences:
- Compensations must be idempotent — they may be retried. A refund operation called twice must not double-refund.
- Compensations must be commutative with concurrent reads — between the original commit and the compensation, other services may have read the value. They saw the original; they need to handle the change.
- Some operations are hard to compensate — sending an email (you can't unsend it; you can send a follow-up), invoking an external API that's not idempotent, or triggering a physical action.
- Compensations can fail too — what if the refund call fails? You need retries, dead-letter queues, and human escalation. Saga orchestration frameworks (Temporal, Cadence, AWS Step Functions) handle this.
Sagas can be coordinated two ways:
- Choreography: each service emits events; the next service listens and proceeds. No central coordinator. Simple to start, hard to follow as the chain grows — debugging "where did this saga go?" requires tracing events across services.
- Orchestration: a central orchestrator (Temporal, Step Functions, a custom state machine) calls each service and handles compensations explicitly. More code, but the workflow is visible in one place and the orchestrator tracks state across retries and crashes.
Most production systems converge on orchestration for complex sagas because visibility and recoverability matter. Choreography works for short, simple workflows where each service can be reasonably assumed to handle its own compensations.
Either way, the saga state must be persisted — not in memory — so a crash mid-saga can resume. The orchestrator (or a saga-state table) tracks which steps completed and which compensations are still owed.
Some operations have no meaningful compensation. Sending an email, mailing a physical product, triggering an SMS — these have side effects in the real world. The pattern handles this by either (a) deferring the irreversible step to the end of the saga, after all compensatable steps have succeeded, or (b) accepting that the operation will happen and compensating with a follow-up (e.g., a cancellation email). Design sagas so irreversible steps are last; if the saga fails before reaching them, no real-world effect occurred.
The classic saga weakness is isolation. Between step 3 (reserve inventory) and step 4 (charge), other queries can see the reserved inventory — they might show 'low stock' or reject a parallel order that would have succeeded if the saga had compensated. ACID's 'I' (isolation) is what sagas give up.
Mitigations:
- Semantic locks: mark records as 'pending' so other queries know not to act on them yet.
- Commutative operations: design steps so concurrent reads don't cause incorrect behavior (e.g., reserve inventory with a tentative count, not a hard decrement).
- Short sagas: keep the saga duration short to minimize the window of inconsistency.
- Compensating reads: queries that need a consistent snapshot read from a projection updated only after saga completion.
This is the fundamental trade-off of sagas: you lose ACID isolation but gain the ability to do transactions across services without distributed locks.
Your checkout saga reserves inventory, then charges the card. The charge fails. What does the compensation look like?
Pick one answer.
Why must compensations be idempotent?
Pick one answer.
Which of these operations is hardest to compensate, and what's the standard pattern to handle it?
Pick one answer.
Engineering mental model
Mental model. Think of Compensating Transaction as a deliberate boundary in a system. The boundary exists because something becomes harder to manage when everything is done in one place: latency, scale, failure isolation, consistency, cost, or team ownership. The useful question is not “what does Compensating Transaction mean?” but “what pressure makes this boundary worth introducing, and what new failure mode does it create?”
Before choosing Compensating Transaction, name the workload, the critical user path, the dominant bottleneck, the failure you are trying to absorb, and the trade-off you are willing to accept. If you cannot name those five things, the design is probably premature.
SELECT id, created_at
FROM records
WHERE tenant_id = ?
ORDER BY created_at DESC
LIMIT 50;
-- Ask: which index makes this query predictable at scale?Back-of-the-envelope reasoning
Numerical lens: write down traffic, payload size, read/write ratio, peak multiplier and durability target before choosing a component. The numbers should justify the architecture.
Interactive thought experiment: Compensating Transaction
Change the variables below and predict what breaks first in Compensating Transaction. The production lab can later reuse these same inputs.
Change one variable at a time. Predict the failure mode first, then move the slider and see whether your mental model matches the simplified system response.
If you are stuck on Compensating Transaction, start by drawing the request path and marking every network hop, stateful component, queue, cache and failure boundary. Then estimate where the system will saturate.
You increase traffic by 10× in a system using Compensating Transaction. What should you inspect first?
Pick one answer.
Which statement is the safest engineering habit when using Compensating Transaction?
Pick one answer.
You have dashboards for traffic, latency, errors and saturation. You can change the architecture, but every change has operational cost.
Production scenario: your system uses Compensating Transaction, traffic suddenly spikes, and p99 latency doubles. What is your first move?
Interview drill
Answer this without notes: When would you choose Compensating Transaction, and when would you intentionally avoid it? Mention at least one bottleneck it addresses, one failure mode it introduces, and one alternative. Then quantify the workload you are designing for.
For Compensating Transaction, start with access patterns rather than brand names. Identify the dominant reads/writes, data relationships, consistency requirements, partition key, hot keys and failure behavior before choosing a storage strategy.
Numerical sanity check
A rough capacity check: required write throughput ≈ peak writes/s × average record size. At 5,000 writes/s and 2 KB average payloads, the raw incoming data stream is about 10 MB/s before indexes, replication and overhead.
Do not optimize for a memorized definition. Reason from the workload and failure mode.
A team proposes Compensating Transaction because it 'scales'. What workload characteristic would make that choice a poor fit?
Pick one answer.
What you gain, what you pay
- +Enables multi-service transactions without distributed locks.
- +Compensations work on already-committed state — no held locks, no cascading failures.
- +Survives orchestrator crashes via persistent saga state.
- +Maps naturally to real business operations (refund, cancel, release).
- −Loss of ACID isolation — intermediate states are visible to other queries.
- −Some operations (emails, physical actions) can't truly be compensated.
- −Compensations can fail too — needs retries, dead-letter queues, human escalation.
- −Operational complexity: orchestrator, saga state, monitoring of stuck sagas.
How this breaks in production
- Non-idempotent compensation retried after a crash double-applies (e.g., double refund).
- Compensation itself fails — saga stuck in a partially-compensated state, needs human intervention.
- Intermediate state observed by another service that acted on it (e.g., showed 'low stock' and rejected a parallel order).
- Irreversible step run mid-saga that can't be compensated when a later step fails.
Don't fall into these traps
- •Treating compensation as rollback — assuming no other system observed the intermediate state.
- •Running irreversible steps mid-saga instead of at the end.
- •Not persisting saga state — orchestrator crash loses the workflow.
- •Not making compensations idempotent — retry causes double-undo.
Real systems using this
How real systems implement this
- Temporal — Workflow orchestration engine that persists saga state across crashes, retries failed activities and compensations, and supports idempotency keys. Built for exactly this pattern.
- AWS Step Functions — State-machine-based orchestration with built-in compensation via Catch/Retry blocks. Each state can have a compensating state triggered on failure.
Practice saying it out loud
- Q1What's the difference between compensation and rollback?
- Q2Why must compensations be idempotent? What goes wrong if they aren't?
- Q3How do you handle operations that can't be compensated (e.g., sending an email)?
- Q4Compare choreography and orchestration for sagas. When would you choose each?
Further reading & references
Core explanations are original NO CAP material. External references are provided for deeper study and standards.
What next?
Mark as understood once the mental model clicks.
Next recommended
Saga Pattern