← Concepts

阅读中文版本

Saga Pattern

A saga coordinates a sequence of local transactions across services, using compensating actions instead of a distributed transaction when a later step fails.

data-consistency

Definition

A saga is a distributed transaction pattern that breaks a business operation into a sequence of local transactions. Each step updates one service and publishes an event or invokes the next step. If a step fails, previously completed steps run compensating actions that semantically undo their work. There is no global lock and no atomic commit across services; the system reaches a consistent state through forward progress or compensation.

Context and scope

Sagas are used for long-running business workflows such as order fulfilment, payments, travel booking, and account provisioning, where holding a distributed lock for the entire operation is impractical and each service owns its own database.

Common misconceptions

  • A saga provides ACID across services — it provides atomicity of outcomes through compensation, not isolation or a global commit
  • A compensating action is a database rollback — it is a new business action that must handle intermediate states and side effects
  • Sagas are always eventually consistent — a poorly designed saga can leave partially visible data unless steps use semantic locks or pending states

Anti-patterns

  • Compensating a step that already produced an irreversible external side effect, such as sending money or an email, without an idempotent reversal
  • Running saga steps without idempotency keys, so retries duplicate work
  • Chaining synchronous calls with no timeouts or persistent state, so a crash loses the workflow position

Related concepts

Related examples

Authoritative source

https://microservices.io/patterns/data/saga.html

Local transactions, global outcome

An order workflow might reserve inventory, charge a payment, and create a shipment. Each service has its own database and cannot participate in a single ACID transaction with the others. A saga makes the workflow explicit: reserve inventory, charge payment, create shipment. If shipping fails, the saga refunds the payment and releases the inventory. Each step commits locally; the saga owns the sequence and the compensation logic.

There are two coordination styles. In orchestration, a central saga executor calls each service and decides what to do next. In choreography, each service reacts to events and publishes the next event; no component owns the whole flow. Orchestration is easier to reason about and observe for complex workflows. Choreography reduces coupling but makes the global flow harder to see as services multiply.

type Step = {
  name: string;
  run: () => Promise<void>;
  compensate: () => Promise<void>;
};

export async function runSaga(steps: Step[]): Promise<void> {
  const completed: Step[] = [];
  try {
    for (const step of steps) {
      await step.run();
      completed.push(step);
    }
  } catch (error) {
    for (const step of completed.reverse()) {
      await step.compensate();
    }
    throw error;
  }
}

This minimal executor is not enough for production. Persist the workflow state and the completed steps before calling the next service, so a process crash can resume or compensate. Make every run and compensate idempotent with a key derived from the saga id and step name. Add timeouts and a terminal failed state that an operator can inspect.

Design the intermediate states

A saga exposes intermediate states: an order may exist before payment succeeds. Model those states explicitly (pending_payment, reserved, cancelled) and decide which are visible to users. A semantic lock marks a record as being modified by a saga so other operations do not act on it as if it were final. This is not isolation, but it prevents the most damaging concurrent updates.

Compensation is not always the inverse of the original action. A refund is a new transaction with its own fees and delays; a released reservation may not restore the original inventory position if other orders consumed it. Design compensations as business operations, test them under partial failure, and make them observable.

When a saga is the right tool

Use a saga when the workflow spans services, is long-running, and can tolerate eventual consistency with explicit intermediate states. The advantage is that each service keeps its own transaction boundary and no global lock is held across network calls. The drawback is that compensation, idempotency, and intermediate states must be designed and operated explicitly. Compared to a distributed transaction, a saga gives up isolation in exchange for availability and independent service evolution. Do not use it to avoid designing a transaction boundary inside one service; a local ACID transaction is simpler and stronger. If the workflow requires strict isolation or immediate global consistency, a saga is the wrong abstraction, and the service boundaries or the business process need to change.