Knowledge Guide
HomeSystem DesignMicroservices Patterns

The Architecture of the Saga Pattern

A saga is a sequence of local transactions. Each local transaction runs inside a single service, commits atomically against that service's own database, and then triggers the next step. There is no global lock and no distributed commit protocol holding the whole thing together; the atomicity you get is per step, not across the saga. What replaces the missing global rollback is an explicit, application-level undo: every forward transaction Tₕ that changes state has a paired compensating transaction Cₕ that semantically reverses it.

So a saga is really two things layered together: a forward path (T1, T2, …, Tₙ) that drives the business transaction toward completion, and a backward path (Cₙ₋₁, …, C2, C1) that unwinds it if a step fails. Because each step commits independently, other transactions can see intermediate state before the saga finishes — the saga trades away the isolation guarantee of a classic ACID transaction (leaving you with roughly A-C-D) in exchange for not holding locks across service and network boundaries.

Compensations are not simple rollbacks

A database rollback discards uncommitted changes; a compensation runs after a commit, so it must produce a new committed state that undoes the effect. Two consequences follow. First, compensations are often semantic rather than exact: cancelling an already-charged payment issues a refund and leaves an audit trail rather than erasing history. Second, and this is the property every compensation must have, a compensation must be idempotent — a recovery process may re-issue it after a crash, and running it twice must have the same effect as running it once.

Idempotency is a blanket requirement. Commutativity is not — it is one specific countermeasure for a specific isolation problem, and we treat it that way in the pitfalls section below rather than demanding it of every compensation.

Two ways to coordinate the steps: orchestration and choreography

The saga structure says nothing about who decides which step runs next. There are two coordination styles.

In orchestration, a dedicated coordinator (an orchestrator, often implemented as a state machine) owns the workflow. It sends a command to each participant, waits for the reply, records progress, and decides the next command — including issuing compensations in reverse order when a step reports failure. Control flow lives in one place.

In choreography, there is no central coordinator. Each service reacts to events and publishes its own events; the next service is subscribed to those events and continues the saga. Control flow is emergent, distributed across the participants, with no single component that knows the whole plan.

diagram
diagram

A traced failure: order #7731

Concrete beats abstract. A customer places order #7731 for two units of SKU-88 at $60 each. The saga runs four local transactions across four services; payment is the step that fails.

StepServiceForward transactionCompensation
T1OrderCreate order #7731, state = PENDINGC1: mark order #7731 FAILED
T2CustomerReserve $120 of creditC2: release $120 of credit
T3InventoryReserve 2 × SKU-88C3: release 2 × SKU-88
T4PaymentCharge card $120 — declined— (nothing committed)

T1, T2 and T3 each commit successfully. At T4 the card is declined, so nothing is charged and there is nothing to compensate for T4 itself. The saga now walks the backward path in reverse order of the successful steps:

  1. C3 — Inventory releases the 2 reserved units of SKU-88.
  2. C2 — Customer releases the $120 credit reservation.
  3. C1 — Order sets #7731 to FAILED (a semantic undo: the row stays for audit, its state is terminal).

Reverse order matters: later steps may depend on earlier ones, so you unwind newest-first. When C1 commits, the system is back to a consistent state — no held credit, no held stock, and an order clearly marked failed.

The saga log and crash recovery

The orchestrator does not keep the saga's progress only in memory — if it did, a crash mid-saga would strand reserved credit and stock forever. Instead it writes a durable saga log: an append-only record of each step starting and finishing. Every forward and compensating transaction is bracketed by a BEGIN and a terminal record (END for success, ABORT for failure), so the log is self-describing and replayable. Here is the log for order #7731:

saga-7731  T1  Order.create        BEGIN
saga-7731  T1  Order.create        END    ok
saga-7731  T2  Customer.reserve    BEGIN
saga-7731  T2  Customer.reserve    END    ok
saga-7731  T3  Inventory.reserve   BEGIN
saga-7731  T3  Inventory.reserve   END    ok
saga-7731  T4  Payment.charge      BEGIN
saga-7731  T4  Payment.charge      ABORT  card_declined
saga-7731  C3  Inventory.release   BEGIN
saga-7731  C3  Inventory.release   END    ok
saga-7731  C2  Customer.release    BEGIN
saga-7731  C2  Customer.release    END    ok
saga-7731  C1  Order.markFailed    BEGIN
saga-7731  C1  Order.markFailed    END    ok

Now suppose the orchestrator crashes right after writing C3 … BEGIN but before C3 … END. On restart it reads the log, sees T4 aborted and C3 in flight with no terminal record, and simply re-runs C3. That is exactly why compensations must be idempotent: releasing the same reservation a second time is a no-op, so the replay is safe. The log turns an in-memory workflow into a recoverable one.

Pitfalls the architecture forces on you

Sagas remove distributed locks, but the problems locks solved do not vanish — they move into your application code.

Lack of isolation (lost updates, dirty reads)

Because each step commits immediately, a concurrent saga or query can read data that a later compensation will undo, or two sagas can overwrite each other. This is the isolation ('I') you gave up. Garcia-Molina and Salem, and later Richardson, catalogue specific countermeasures you apply per case — semantic locks (mark a record with a PENDING flag so others know it is in-flight), commutative updates (design the operation so order does not matter, e.g. debit/credit rather than set-balance), pessimistic ordering of steps, reread value before updating, version file (record operations and reorder them), and by value (route high-risk requests through a stricter path). Commutativity is one entry on this list — a targeted fix for concurrent-access anomalies — not a property demanded of every compensation.

Non-compensatable and pivot steps

Some actions cannot be undone: sending an email, calling a non-refundable external API, shipping a package. The standard structure is to order the saga as compensatable steps, then a single pivot step (the point of no return — once it commits the saga must go forward), followed by retriable steps that are guaranteed to eventually succeed. Put anything irreversible at or after the pivot.

Failed compensations

A compensation can itself fail (the service is down). There is no compensation-for-a-compensation, so the recovery machinery must retry it until it succeeds — which again requires idempotency — and escalate to alerting or manual intervention as a last resort.

Choreography-specific hazards

With no central coordinator, control flow is spread across event subscriptions. This invites event storms and hard-to-trace cascades, and cyclic dependencies where services indirectly trigger each other. And because messaging is typically at-least-once, handlers must cope with duplicate and out-of-order events — deduplicate via a processed-message table and keep handlers idempotent.

Choosing: saga vs 2PC, and orchestration vs choreography

Saga vs two-phase commit (2PC)

2PC gives you a genuine atomic distributed commit with full isolation, but at a price: a coordinator that is a single point of failure, participants that hold locks and block while awaiting the commit decision, a hard dependency on XA-style transaction support across every store, and poor availability under network partitions. It fits a small number of resources inside one latency and trust boundary, for short transactions where strong isolation is non-negotiable.

A saga holds no distributed locks, so it stays available and scales across heterogeneous and autonomous services — but it gives up isolation, only guarantees eventual consistency, and pushes real work onto you (writing compensations, ordering pivots, defending against anomalies). Choose a saga for long-lived business transactions spanning independent services where availability matters more than immediate global consistency.

Orchestration vs choreography

 OrchestrationChoreography
Control flowCentralized in a coordinator/state machineEmergent from events, decentralized
Visibility & debuggingEasy — one place shows the whole flowHard — flow is scattered across services
CouplingParticipants coupled to the orchestratorLoosely coupled via events
Main riskOrchestrator becomes a bloated 'god' serviceEvent storms and cyclic dependencies
Choose whenMany steps / complex branching; you need central monitoring, testing, and clear failure handlingFew participants and a simple, mostly linear flow; teams want autonomy and minimal shared infrastructure

A common rule of thumb: start with choreography for simple sagas of two or three participants, and move to orchestration once the number of steps or the branching logic makes the implicit flow hard to follow.

Sources: Adapted and substantially expanded from the original lesson. Primary references: Chris Richardson, Microservices Patterns (Manning, 2018), Chapter 4, “Managing transactions with sagas”; and Hector Garcia-Molina and Kenneth Salem, “Sagas,” Proceedings of the ACM SIGMOD International Conference on Management of Data (1987), which introduces the compensation model and the concurrent-access countermeasures (semantic locks, commutative updates, reread value, version file) referenced above.

🤖 Don't fully get this? Learn it with Claude

Stuck on The Architecture of the Saga Pattern? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **The Architecture of the Saga Pattern** (System Design) and want to truly understand it. Explain The Architecture of the Saga Pattern from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **The Architecture of the Saga Pattern** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **The Architecture of the Saga Pattern** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **The Architecture of the Saga Pattern** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes