Saga Pattern (Distributed Transactions)

Keep data consistent across several services without a distributed transaction: a chain of local transactions, each with a compensating step that undoes it if a later step fails.

Overview

A saga is a business operation that spans several databases, written as a sequence of ordinary local transactions. Each step commits on its own. If a later step fails, the saga runs a compensating transaction for every step that already committed, newest first, so the system ends in a state the business accepts.

The idea comes from Hector Garcia-Molina and Kenneth Salem, in the paper *Sagas* (ACM SIGMOD, 1987). They were solving a problem inside a database system: a long-lived transaction that holds locks for hours blocks everyone else and is more likely to deadlock or hit a crash. Their answer was to split it into steps that release their locks as they go, and to pair each step with code that can undo it. About thirty years later the same idea became a common way to run one business operation across microservices that each own a database.

Interviewers ask about sagas because the pattern forces you to say what you give up. A saga is not a transaction. It has no isolation, its rollback is code you write, and the coordinator can crash halfway. A strong answer names those costs and shows the design that pays for each one.

The problem: one operation, three databases

Placing an order touches three services. Each has its own database, so no single database transaction can cover the whole operation.

There are three ways to get all-or-nothing behavior across those writes.

  • One database — One ACID transaction covers every table — Every service shares a schema; the split into services is gone
  • Two-phase commit (2PC) — A coordinator asks every participant to prepare, then tells all of them to commit — Participants hold locks from prepare until the decision arrives; if the coordinator crashes in between they wait, blocked
  • Saga — Each service commits its own step; failures are undone by compensating steps — No isolation, eventual consistency, and rollback code you must write and test

What a saga guarantees

Garcia-Molina and Salem define a saga as transactions T1 to Tn with compensating transactions C1 to Cn-1. The system promises exactly one of two outcomes.

  • Success — T1, T2, ..., Tn — Every step committed
  • Abort after step j — T1, ..., Tj, Cj, ..., C1 — The first j steps committed, then each was compensated in reverse order

Two details from the paper matter in interviews. The last step needs no compensating transaction, because the saga ends when it commits and nothing runs after it. And other transactions may see the effects of T1 to Tj before Cj to C1 run; the paper says plainly that no effort is made to notify or abort them. That is the missing isolation, covered below.

Happy path

The orchestrated version is the easiest to read: one coordinator sends a command per step and waits for the reply.

Compensating rollback

The card is declined at T3. Steps T1 and T2 have already committed, so the orchestrator runs C2 and then C1. The paper calls this backward recovery.

The paper also names the other choice, forward recovery: run the missing steps instead of undoing the finished ones, which needs a save point to restart from and the code for the remaining steps. A real orchestrator retries a step that failed for a technical reason (a timeout, a crashed worker) and compensates only for failures that will not go away, such as a declined card.

A saga you can run

The program below is the whole mechanism: a list of steps, each with an action and a compensation, and a runner that undoes completed steps in reverse when one fails. The two failing runs leave stock, orders and charges exactly as the business expects.

Notice three things in the code. The compensation for reserve_stock adds the quantity back; it does not write the old value 5, because order 41 has a reservation of its own by then. A step's own failure never needs its own compensation, because a step that failed did not commit: order 42's declined charge is not refunded, only the steps before it are undone. And charge_card still needs refund_charge, because a step after it (approve_order) can fail, as order 43 shows; only the last step has nothing after it, so it is the only step without a compensation.

Compensation is semantic, not a restore

The paper's airline example makes the point. If T2 reserves a seat, C2 cancels that reservation. C2 must not write back the seat count that existed before T2, because other transactions have run in between. A compensating transaction undoes the meaning of a step, not its bytes, and it does not promise to return the database to the exact earlier state.

  • Reserve 2 units — Release 2 units — Set stock back to 5
  • Create order PENDING — Mark it CANCELED — Delete the row (the audit trail is gone)
  • Charge the card — Refund the charge — Pretend the charge never happened; the statement shows both
  • Send a confirmation email — Send a cancellation email — Nothing can unsend it

The last row is why step order matters. Put steps that cannot be compensated (emails, shipping, third-party calls) last, after every step that can still fail.

Choreography or orchestration

The two coordination styles move the same steps but put the control flow in different places.

  • Who decides the next step — Each service, by subscribing to events — One orchestrator, by sending commands
  • Where the flow is written — Spread across every participant — In one place, readable top to bottom
  • Coupling — Services know only event names — Services know the orchestrator's commands; the orchestrator knows every service
  • Compensation logic — Each service must handle every failure event that concerns it — The orchestrator holds the undo order
  • Timeouts and retries — Implemented in each service separately — Implemented once, in the orchestrator
  • Single point of failure — None — The orchestrator, unless it runs on a durable engine
  • Fits — Two or three participants, simple flow — Many steps, branching, or when someone must be able to see the saga's state

Chris Richardson's microservices.io catalog names both styles; AWS's guidance adds that choreography gets hard to follow as participants grow and can develop cyclic event dependencies, while an orchestrator is a single point of failure unless it runs on a managed workflow service such as AWS Step Functions. Temporal makes the same trade: a workflow is durable execution that resumes after a crash from its event history, and its docs point to a workflow when you need to compensate for a partial failure.

What a saga gives up: isolation

Between T2 and C2 the stock looks lower than it will be; between T1 and C1 the order looks real. Any other saga or query that reads in that window sees a partial result. Richardson calls the fixes countermeasures: design techniques that put back some isolation by hand.

  • Dirty read — A report counts order 42 as a sale while its payment is still pending — Semantic lock: a PENDING state that readers know to skip or show as provisional
  • Lost update — An address change lands on order 42 while the saga is canceling it — Semantic lock: reject or queue writes to a row that is inside a saga
  • Non-repeatable read — A step reads stock, a concurrent saga changes it, the next step acts on the stale value — Re-read the value in the step that acts on it, or make updates commutative (add and subtract rather than set)

The PENDING state in the example above is the semantic lock. It costs one column and one check, and it is the countermeasure interviewers expect you to name.

Surviving a crash

The paper's design has a saga execution component that records every saga command in a log before acting on it, and the log is kept in two copies. After a crash it scans the log, finds the last committed and uncompensated step, and either compensates from there (backward recovery) or, given a save point and the remaining code, restarts the saga from that point. Modern engines do the same with a persisted history.

  • A participant crashes after committing but before replying — The orchestrator retries the step and it runs twice — Every step and compensation is idempotent: keyed by saga id, safe to repeat
  • The orchestrator crashes mid-saga — Steps already committed have no one to finish or undo them — A durable log or workflow engine that resumes from the last logged transition
  • A service commits its step but the event or reply is lost — The saga stalls — Transactional outbox: write the outgoing message in the same local transaction as the data
  • A compensation itself fails — The saga is stuck half undone — Retry compensations until they succeed; they must be written so that they cannot fail for business reasons

What you will be able to do

  • Define a saga in one sentence and attribute it to Garcia-Molina and Salem (1987).
  • Draw the happy path and the compensating rollback for an order flow, and say why compensation runs newest first.
  • Choose choreography or orchestration for a given flow and defend the choice with two trade-offs.
  • Name the isolation anomalies a saga allows and the semantic lock that contains them.
  • Explain what idempotency, a saga log and the transactional outbox each protect against.

Interview questions

  • Walk through an order saga and what happens when the payment is declined.
  • Choreography or orchestration: which would you pick, and why?
  • What must a compensating transaction satisfy? Show a bad one and a good one.
  • How is a saga different from two-phase commit?
  • Sagas have no isolation. What goes wrong, and how do you defend against it?
  • The orchestrator crashes after step 2 of 4. How does the saga finish?

Sign up free to read the full topic

More Low-Level Design topics

Start free on Cariara · Coding problems · System design studio