Architecture

Saga Pattern

Managing distributed transactions across services using a sequence of local transactions with compensating actions.

Overview

The Saga pattern manages distributed transactions across multiple services without a two-phase commit. A saga is a sequence of local transactions; if one fails, compensating transactions are executed in reverse to undo the work already done. There are two coordination styles: Choreography (services react to events) and Orchestration (a central coordinator commands each step).

Origin

The term "saga" in this context was introduced by Hector Garcia-Molina and Kenneth Salem in "Sagas" (1987) for long-lived database transactions. It was rediscovered for microservices by Chris Richardson and others around 2018.

Examples

Orchestrated saga for order fulfilment

class PlaceOrderSaga {
  async execute(order) {
    const steps = [
      { do: () => paymentService.reserve(order),   undo: (r) => paymentService.release(r.reservationId) },
      { do: () => inventoryService.reserve(order), undo: (r) => inventoryService.release(r.reservationId) },
      { do: () => shippingService.schedule(order), undo: (r) => shippingService.cancel(r.shipmentId) },
    ]

    const completed = []

    for (const step of steps) {
      try {
        const result = await step.do()
        completed.push({ step, result })
      } catch (err) {
        // Compensate in reverse order
        for (const { step: s, result: r } of completed.reverse()) {
          await s.undo(r).catch(e => compensationLog.error(e))
        }
        throw new SagaFailedError(err)
      }
    }
  }
}

Compensating transactions must be idempotent, they may be executed more than once if the orchestrator crashes and retries. Use idempotency keys.

Use Cases

  • 01Order placement: reserve payment, reserve inventory, schedule shipping, all must succeed or all must roll back
  • 02User onboarding across multiple services: create account, send welcome email, initialise preferences
  • 03Booking systems: hold seat, charge card, issue ticket, distributed across separate services

When Not to Use

  • //When a single database transaction is possible, ACID guarantees are far simpler and more reliable
  • //When compensating transactions cannot be defined, some side effects (sent emails, fired webhooks) cannot be undone
  • //When the failure mode analysis of all compensation paths is too complex to reason about safely

Technical Notes

  • Choreography vs Orchestration: choreography is more decoupled but harder to observe (the saga has no explicit owner); orchestration is easier to debug but the orchestrator is a central dependency
  • Compensating transactions are not rollbacks in the ACID sense, they produce new transactions that reverse the effect. They run in a future transaction, not the same one
  • Saga state must be persisted so that if the orchestrator crashes mid-saga, it can resume or compensate after restart