beginner~1h

The Saga Pattern — Why We Need It

Understand why a single distributed transaction across several microservices is impractical, and learn how the saga pattern replaces it with a sequence of local transactions plus compensating actions when something fails partway through.

Learning objectives

  • Explain why a cross-service ROLLBACK isn't possible in a microservices architecture
  • Walk through a multi-step saga and its compensating transaction when a step fails
  • List the benefits and drawbacks of the saga pattern compared to a single distributed transaction
  • Distinguish a saga's eventual consistency from the immediate consistency of a single database transaction

◆ Imagine this

Planning a wedding involves several independent vendors — a caterer, a florist, a venue — none of whom report to each other or share a common "wedding database." If the venue cancels at the last minute, the event planner doesn't have a magic "undo the whole wedding" button. Instead, they take deliberate, specific action: cancel the florist's order, cancel the caterer's booking, and refund the deposit — each a separate, real action undoing something that had already been arranged.

That's exactly the saga pattern's core idea, applied to software. There's no cross-service "rollback" button in a microservices architecture — instead, a failure partway through triggers deliberate, planned actions that undo each step that had already completed successfully.

A loan approval genuinely requires three independent services to each do their part, one after another, with no single service able to complete the whole thing alone:

StepServiceAction
1LoansCreate the loan record
2AccountsDeposit the approved loan amount into the customer's account
3CustomerUpdate the customer's credit profile to reflect the new debt

If step 2 fails — say, the customer's account was closed moments earlier — the saga needs to undo step 1: cancel the loan record that was just created, since a loan record with no corresponding deposit is no longer valid. Note what didn't happen here: there was never a single, giant transaction spanning all three services that could simply be rolled back. Step 1 genuinely committed, on its own, before step 2 was ever attempted — and undoing it now requires a real, separate action, not a database ROLLBACK.

A saga replaces one impossible cross-service transaction with a sequence of local transactions, each one fully committed on its own service, one after another. There's no coordinator holding a lock across all three services at once — each step genuinely finishes, completely, before the next one even starts. If a later step fails, the saga runs compensating transactions: new, forward-moving actions that reverse the effect of whichever earlier steps already succeeded.

Working through the loan example: step 1, creating the loan, succeeds and commits. Step 2, depositing the funds, fails. Rather than attempting any kind of rollback across services — which isn't possible, since step 1 is already a completed fact on the loan service — the saga runs a compensating transaction that explicitly cancels the loan record step 1 created. This is a genuinely new action moving the system forward to a consistent state, not an undo of history.

This coordination can be implemented in two different ways: choreography, where there's no central coordinator and each service simply reacts to events on its own, watching for the ones relevant to it; and orchestration, where one central coordinator explicitly directs every step and every compensation in sequence. Both achieve the same underlying local-transactions-plus-compensation idea; they differ in where the coordination logic actually lives.

BenefitsDrawbacks
Each service stays independently available — no cross-service locking, unlike a two-phase commitOnly eventually consistent — there's a real window where the loan exists but the deposit hasn't happened yet
Works naturally with an event-driven, Axon-based architectureCompensating transactions must be designed deliberately for every step — genuinely more upfront thinking required
Scales well — no shared coordinator holding locks across servicesDebugging a failed, partially-compensated saga is harder than debugging one failed local transaction

The eventual-consistency drawback deserves a second look: between step 1 committing and step 2 either succeeding or triggering a compensation, the system is genuinely, if briefly, in an inconsistent-looking state — a loan record exists with no matching deposit yet. Any code reading that data during this window needs to be written with that possibility in mind, rather than assuming a loan record always implies a completed deposit.

▲ Common mistake — treating a compensating transaction as an undo

A compensating transaction is a new action, not a reversal of history. Canceling a loan record doesn't erase the fact that it was briefly created and committed — it adds a new fact, a cancellation, on top. Any code, reporting, or audit trail relying on the loan history needs to account for records that were created and then compensated, not assume every record represents a step that stayed successful forever.

▲ Edge case — compensations can fail too

A compensating transaction is still a real operation against a real service, and it can fail just like the original step did — the account service performing the cancellation could itself be briefly unavailable. A saga implementation needs a strategy for this case too, typically retrying the compensation until it succeeds, since leaving a step half-compensated is often worse than a slow, eventually-successful cleanup.

▲ Common mistake — skipping compensation design until something fails in production

Because every step in a saga needs an explicit compensating action designed in advance, it's a mistake to build the "happy path" first and treat compensation as an afterthought. A step with no sensible compensating action at all — some operations, like sending an email, can't truly be "undone" — needs to be identified and designed around before the saga goes live, not discovered during an incident.

Sagas show up anywhere a single business process genuinely spans several independently-owned services with no shared database: a loan approval touching loans, accounts, and customer profile services; an e-commerce checkout touching inventory, payment, and shipping services; a travel booking touching flight, hotel, and car-rental reservations, each of which needs its own explicit cancellation logic if a later step in the trip fails.

The choice between choreography and orchestration tends to track team and process complexity. A short saga with two or three steps and few conditional branches often reads clearly as choreography, with each service simply reacting to the event before it. A longer saga with many steps, conditional branching, or a need for centralized visibility into "where is this specific loan approval right now" tends to favor orchestration, where one coordinator's state machine makes the current step explicit and easy to query.

  • Q: What replaces a cross-service ROLLBACK in the saga pattern? A: A compensating transaction — a new, forward-moving action that deliberately reverses the effect of an already-completed earlier step.
  • Q: Why is a saga only "eventually" consistent, rather than immediately consistent? A: Each step is a genuinely separate local transaction, completed one after another — there's a real window of time where earlier steps have committed but later ones haven't happened yet.
  • Q: What are the two ways to implement saga coordination? A: Choreography, where there's no central coordinator and each service reacts to events on its own, and orchestration, where one central coordinator explicitly directs every step.
  • Q: Can a compensating transaction itself fail? A: Yes — it's a real operation against a real service and can fail like any other step, so a saga needs a strategy, typically retrying, for handling a failed compensation.
  • Q: Why does a saga scale better than a single distributed transaction using a two-phase commit? A: Because no coordinator holds a lock across every participating service at once — each local transaction commits fully and independently before the next step starts, so no service is blocked waiting on another.

Want a visual for this concept?

Generate a diagram tailored to “The Saga Pattern — Why We Need It” — the AI picks whichever visual (flowchart, comparison, sequence, etc.) best fits.

Sign in to generate a visual →

Practice quiz

Next Step

Continue to Choreography Saga← Back to all Event-Driven Microservices chapters