Back to the map

Module 14 · Distributed transactions · Lesson 4 of 5

Sagas

A long business transaction as a sequence of local transactions, each with a compensating action that undoes it if a later step fails. Orchestration versus choreography, designing compensations and pivot steps.

DeepMust-knowSenior 14 minVideoPro Updated Nov 2026

The video walkthrough is part of Pro.

See what Pro includes
Video walkthrough · 2:46 · narrated, with captions

Why it matters

Order, pay, reserve, ship: four services, four databases, one business outcome. Two-phase commit across them would hold locks across network calls and block on coordinator failure, and the payment provider cannot prepare anyway. The saga is how real systems make multi-service workflows reliable: each step commits locally, and if something fails later the earlier steps are undone by explicit compensations. It is the pattern interviewers expect for any cross-service business flow, and the details (compensations, idempotency, intermediate states) are where the real design lives.

The idea

A saga is a sequence of local transactions T1, T2, ... Tn, each in one service, with compensating transactions C1, C2, ... that semantically undo their counterparts. If Tk fails, the saga runs Ck-1 down to C1 in reverse order. The whole flow is eventually consistent: between steps, the system is in an intermediate state that other parts of the system can observe and must be designed to tolerate.

T1 create orderpendingT2 reserve stockT3 charge cardT4 confirmdeclinedC2 release stockC1 cancel orderevery T and C is idempotent and recorded in the saga log
Steps commit locally in sequence. A failure at step 3 triggers compensations for steps 2 and 1 in reverse, each a normal local transaction.

Two ways to coordinate

 OrchestrationChoreography
ControlA central orchestrator sends commands to each service and decides the next step or the compensation pathEach service reacts to the previous step's event and emits its own; no central brain
VisibilityThe whole flow is explicit in one place; saga state answers 'where is order 9001'The flow is implicit across services; tracing requires correlating events
CouplingServices know only their commands; the orchestrator knows everyoneServices know which events to consume and emit; adding a step may touch several
Failure handlingCentralised timeouts, retries and compensation sequencingEach service must handle its own compensation triggers
FitsFlows with several steps, branching, or compensationsTwo or three loosely coupled reactions
ToolingWorkflow engines (Temporal, Camunda, Step Functions) or a hand-built state machineMessage broker and conventions

For anything beyond three steps, orchestration is usually the better engineering choice because the flow is inspectable and changeable in one place. The orchestrator must itself be durable: its state in a replicated store, its progress resumable after a crash.

Designing the steps

  1. 1

    Make every step idempotent

    Steps and compensations will be retried after timeouts. Key them on the saga id so repeats are no-ops: reserving stock twice for saga 9001 reserves once.
  2. 2

    Design compensations as business actions

    A compensation is not a rollback; it is a new forward action with business meaning: refund, release, cancel, send an apology. It must be possible even after other things have happened, which is why some actions cannot be compensated (an email cannot be unsent) and must be moved after the pivot.
  3. 3

    Order steps: compensatable, pivot, retryable

    Put steps that can be undone first; then the pivot step, which once done means the saga will complete (the charge, typically); then steps that cannot fail permanently and are simply retried until they succeed (send confirmation, update analytics). Nothing after the pivot needs a compensation.
  4. 4

    Keep a saga log

    Record each step's start and outcome durably before moving on, so a crashed orchestrator resumes correctly and never re-runs a completed pivot.
  5. 5

    Use semantic locks for intermediate state

    Mark the order as pending so other flows (a second checkout, a cancellation) see that a saga owns it and either wait or reject, since there is no isolation across services.
  6. 6

    Bound the saga

    Give reservations expiries and sagas deadlines, so a stalled saga releases resources and lands in a known failed state instead of holding stock forever.

Messaging and the outbox

Each step's local transaction and the message that triggers the next step must not be split by a crash. The transactional outbox, next lesson, writes the message in the same database transaction as the step's state change and relays it afterwards. Combined with idempotent consumers, this gives sagas reliable at-least-once messaging without 2PC.

When things go wrong anyway

Compensations can fail too. Retry them with backoff; if they keep failing (the refund API is down for hours), park the saga in a manual-intervention state with a dead-letter record and alert a human. Never let a saga end silently half-done. Expose saga state on a dashboard keyed by the saga id, carry that id as the correlation id on every message, and alert on sagas stuck in a state beyond a threshold. Sagas replace database isolation with visibility, so observability is part of correctness.

Sagas · Backplane