Skip to main content

Sagas: Orchestration vs Choreography & Compensation

Level 5: Level 5: Distributed Systems Corehard35 minsagacompensationorchestration

Chain local transactions with compensating undos: atomicity of outcome without isolation, contained by semantic locks, with idempotent compensations backed by retries and a DLQ.

Local transactions, compensating undos

When a business transaction spans services and 2PC is too blocking, the standard answer is a saga: a sequence of local transactions, one per service, where each step has a compensating action that semantically undoes it. There is no global lock and no global commit. You make forward progress step by step, and if a later step fails you run the compensations for the steps that already succeeded, in reverse. A saga gives you atomicity of outcome (fully done or fully undone) but crucially not isolation.

Orchestration vs choreography

  • Orchestration: a central orchestrator (a Temporal/Cadence workflow, an AWS Step Functions state machine) explicitly calls step 1, step 2, step 3, and on failure invokes the compensations. Pros: the flow lives in one place, easy to reason about, trace, and add timeouts/retries. Cons: the orchestrator is a component you must run and make reliable.
  • Choreography: no central brain. Each service listens for events and reacts: Order emits OrderCreated, Inventory reacts and emits InventoryReserved, Payment reacts, and so on. Pros: highly decoupled. Cons: the end-to-end flow is implicit, scattered across services, hard to trace and debug, especially for compensations. Cyclic event dependencies sneak in.

Rule of thumb: choose orchestration for anything with more than a couple of steps, non-trivial compensation logic, or where on-call must be able to see "where is this order stuck?" Choose choreography only for short, simple, truly decoupled flows.

Check yourself
An order saga has finished step 1, reserve inventory, and is about to run step 2, charge the card. Right now another request reads that inventory row. What does it see?

The interview-critical property: no isolation

Between steps, intermediate states are visible to other transactions. In an order saga, inventory is reserved (visible) before payment succeeds; another request can observe "reserved but unpaid." This is a real anomaly a single ACID transaction would never expose. Manage it with countermeasures:

  • Semantic lock: mark a record with a pending/in-saga flag (order status PENDING) so others treat it as tentative.
  • Commutative updates: design operations so order does not matter (increment/decrement rather than absolute set).
  • Reread / version check: verify a version/state before compensating, so you compensate against current reality, not a stale snapshot.
Check yourself
A saga step shipped a package and emailed the customer a confirmation. A later step fails and compensation kicks in. What does compensating that step look like?

Compensations are their own hazard

A compensation must be idempotent (it may be retried) and it may itself fail. "Un-charge a card" is fine as a refund, but "un-send an email" or "un-ship a package" is not truly reversible, so you compensate semantically (issue a recall, send an apology, restock on return). For compensations that fail: retries with backoff, a dead-letter queue, and ultimately operator escalation. This durability and retry machinery is exactly what Temporal / Step Functions give you for free.

Interview nuance: the two things interviewers probe are (1) "sagas give atomicity but not isolation, what anomaly does that allow and how do you contain it?" and (2) "what happens when a compensation fails?" Concrete answers to both put you ahead of most candidates.

Recap: a saga chains local transactions each with a compensating undo, coordinated centrally (orchestration, preferred for anything non-trivial) or via events (choreography); it guarantees the outcome is all-or-nothing but exposes intermediate state, so you add semantic locks and version checks, and make compensations idempotent with retries, DLQ, and escalation.

Check yourself

Last check before you design your own saga: attribute each property.

The end-to-end flow lives in one place, and on-call can see exactly where an order is stuck
Services react to each other's events with no central brain, and cyclic dependencies can sneak in
Intermediate state is visible to other requests between steps
Every step needs an idempotent compensating action that may itself fail

Apply

Your turn

The task this lesson builds to.

Design an order-checkout saga that reserves inventory, charges payment, and books shipping across three services, with compensations, and specify the exact behavior when payment fails after inventory has already been reserved.

Think about

  1. What does a saga give (atomicity of outcome) and NOT give (isolation)?
  2. Orchestration vs choreography: which do you pick and why?
  3. How do you handle non-idempotent or failing compensations?

Practice

Make it stick

A second problem on the same idea, so it survives past today.

Design the booking saga for a service like Expedia or Booking.com that reserves a flight, a hotel, and a rental car from three independent third-party suppliers in one trip, where any supplier can be slow or reject the booking and some confirmations are effectively non-reversible. Lead with the deliverable, then walk the compensation and isolation strategy.

Think about

  1. How do hold-then-confirm APIs change the compensation story?
  2. Where do you sequence the non-reversible step, and why?
  3. What do you do when a supplier call times out ambiguously?