logo

Saga Pattern for Distributed Transactions

The saga pattern coordinates transactions that span multiple services without relying on distributed locks. Each service performs its own local transaction and publishes an event. If any step fails, previously completed steps are reversed through compensating transactions that undo their effects.

How It Works

A saga breaks a single distributed transaction into a sequence of local operations. Each operation has a matching compensation action that can reverse it.

Forward flow (success):
Step 1: Reserve flight → Step 2: Reserve hotel → Step 3: Charge payment
Backward flow (Step 3 fails):
Compensate 2: Cancel hotel → Compensate 1: Cancel flight

If every step succeeds, the saga completes normally. If a step fails, the system walks backward through all completed steps and runs their compensations.

Forward Recovery vs. Backward Compensation

Two strategies handle failures within a saga:

Backward compensation reverses completed work when a step fails. This is the classic approach - undo everything and return to the starting state.

const saga = [
{ execute: reserveFlight, compensate: cancelFlight },
{ execute: reserveHotel, compensate: cancelHotel },
{ execute: chargePayment, compensate: refundPayment },
];
async function runSaga(steps, context) {
const completed = [];
for (const step of steps) {
try {
await step.execute(context);
completed.push(step);
} catch (error) {
// Walk backward through completed steps
for (const done of completed.reverse()) {
await done.compensate(context);
}
throw new SagaAbortedError(error);
}
}
}

Forward recovery retries the failing step until it succeeds. This works when the business outcome requires completion rather than rollback. Combine this with retry policies and timeouts to avoid indefinite loops.

Orchestration vs. Choreography

Sagas can be coordinated in two ways:

  • Orchestration: A central coordinator directs each step in order. Easier to reason about and debug. The coordinator holds the full workflow definition.
  • Choreography: Each service listens for events and acts independently. More decoupled but harder to trace across services.

Designing Good Compensations

Compensating transactions must be idempotent. A compensation might run more than once if the system crashes mid-rollback. Design each compensation so that running it twice produces the same result as running it once.

Not every action has a clean reversal. Sending an email cannot be unsent. In these cases, place irreversible steps last in the saga or use follow-up actions like sending a cancellation notice.

When to Use the Saga Pattern

  • Business processes that span multiple services with independent data stores
  • Long-running workflows where holding locks would block other operations
  • Booking and reservation systems with multi-step fulfillment
  • Order processing pipelines that touch payment, inventory, and shipping