What Is a Saga Pattern?

By Oleksandr Andrushchenko — Published on
0 Likes
0 Dislikes

The Saga pattern is a way to manage a business transaction that spans multiple services without using one distributed database transaction. Instead of locking every participating resource until the entire operation completes, a saga breaks the workflow into a sequence of local transactions.

Each local transaction commits independently. If a later step fails, previously completed steps are handled through compensating transactions that logically undo or counteract their business effects.

Table of Contents

Why the Saga Pattern Exists

A traditional application can often perform a business operation inside one database transaction.

BEGIN

Create Order
Reserve Inventory
Create Payment

COMMIT

If any statement fails, the database can roll back the entire transaction.

Microservice architectures make this more difficult because each service typically owns its own data.

Order Service      → Orders DB
Inventory Service  → Inventory DB
Payment Service    → Payments DB
Shipping Service   → Shipping DB

Creating an order may require changes across all four services:

Create Order
    ↓
Reserve Inventory
    ↓
Charge Payment
    ↓
Create Shipment

There is no ordinary local database transaction covering all of these databases.

A saga handles the workflow as a series of independently committed operations while defining what should happen if the complete business process cannot finish.

This is one of the central challenges of Managing Data Across Multiple Services.

How a Saga Works

A saga represents one business operation as multiple transactions:

T1 → T2 → T3 → T4

Each transaction belongs to one service and commits to that service's local database.

If every transaction succeeds, the saga completes.

T1 ✓
 ↓
T2 ✓
 ↓
T3 ✓
 ↓
T4 ✓
 ↓
Saga Completed

If a later operation fails, compensating actions can be executed for earlier operations.

T1 ✓
 ↓
T2 ✓
 ↓
T3 ✗

Compensate T2
      ↓
Compensate T1

Local Transactions

Each saga step should normally use a regular local transaction inside the service responsible for that data.

For example, the Inventory service can atomically reserve stock:

BEGIN;

UPDATE inventory
SET available = available - 1,
    reserved = reserved + 1
WHERE product_id = 8472
  AND available > 0;

COMMIT;

The transaction does not need to lock the Payment or Order databases.

Once committed, however, that reservation is real. A later failure elsewhere cannot simply roll back the original database transaction.

Compensating Transactions

A compensating transaction performs a business operation that counteracts an earlier completed step.

Examples include:

Original Operation Compensating Operation
Reserve inventory Release inventory
Charge payment Refund payment
Create reservation Cancel reservation
Allocate resource Release resource
Create pending shipment Cancel shipment

Compensation is not necessarily the exact technical inverse of the original operation.

For example:

Charge Credit Card
        ↓
Payment Processor Settles Charge
        ↓
Later Saga Failure
        ↓
Issue Refund

The original charge cannot be erased from history. A new refund transaction compensates for its business effect.

Successful Saga Example

Consider an e-commerce checkout.

The workflow contains four steps:

1. Create Order
2. Reserve Inventory
3. Charge Payment
4. Create Shipment

A successful execution might look like:

Order Service
Create order: PENDING
        ↓
Inventory Service
Reserve products
        ↓
Payment Service
Charge customer
        ↓
Shipping Service
Create shipment
        ↓
Order Service
Mark order: CONFIRMED

Each service commits its own state independently.

There is no database transaction that remains open across the entire workflow.

The order might move through explicit states:

PENDING
   ↓
INVENTORY_RESERVED
   ↓
PAYMENT_COMPLETED
   ↓
CONFIRMED

These intermediate states are important because a saga is a process that can remain incomplete for seconds, minutes, or even longer.

What Happens When a Saga Fails?

Suppose inventory reservation and payment both succeed, but shipment creation fails.

Create Order       ✓
Reserve Inventory  ✓
Charge Payment     ✓
Create Shipment    ✗

The system now has committed state in several services.

Simply returning an HTTP 500 response does not restore consistency.

The saga can begin compensation:

Create Shipment    ✗
        ↓
Refund Payment
        ↓
Release Inventory
        ↓
Cancel Order

After successful compensation, the final business state becomes consistent again:

Order     = CANCELLED
Inventory = AVAILABLE
Payment   = REFUNDED
Shipment  = NOT CREATED

Notice that the system passed through inconsistent intermediate states.

For some period:

Payment = CHARGED
Shipment = MISSING

This is expected in a saga-based architecture. The design must explicitly account for these temporary states rather than assuming atomic visibility across services.

Orchestration vs Choreography

There are two common ways to coordinate saga steps: choreography and orchestration.

Understanding the Saga Pattern: Orchestration vs Choreography
Understanding the Saga Pattern: Orchestration vs Choreography

Saga Orchestration

In orchestration, a dedicated saga orchestrator decides which step executes next.

               Saga Orchestrator
                /      |      \
               ↓       ↓       ↓
            Order   Inventory Payment
                              |
                              ↓
                           Shipping

The orchestrator might execute:

CreateOrder
     ↓
ReserveInventory
     ↓
ChargePayment
     ↓
CreateShipment

If payment fails:

ChargePayment ✗
      ↓
ReleaseInventory
      ↓
CancelOrder

The orchestrator owns the workflow state and knows which compensations are required.

This usually makes complex workflows easier to inspect and reason about, but it introduces another important component whose state must be durable and recoverable.

Property Choreography Orchestration
Coordinator Distributed among services Explicit orchestrator
Communication Usually events Commands, replies, and/or events
Simple workflows Often convenient May add unnecessary structure
Complex workflows Can become difficult to follow Usually easier to model explicitly
Workflow visibility Distributed Centralized

Saga Choreography

In choreography, services react to events and publish new events without one central component controlling the entire workflow.

OrderCreated
     ↓
Inventory Service
     ↓
InventoryReserved
     ↓
Payment Service
     ↓
PaymentCompleted
     ↓
Shipping Service

For example:

{
  "event": "inventory.reserved",
  "saga_id": "saga-9481",
  "order_id": "order-4721"
}

The Payment service subscribes to this event, charges the customer, and publishes another event.

This approach fits naturally with Event-Driven Architecture in Distributed Systems.

Choreography can work well for short workflows with clear event relationships and relatively few participants.

Its main risk is that the overall business process becomes distributed across many service handlers.

Service A knows event B
Service B knows event C
Service C knows event D
Service D knows compensation E

As the workflow grows, understanding the complete state machine can become difficult.

Saga State and Durability

A production saga cannot rely on an in-memory function such as:

reserve_inventory()
charge_payment()
create_shipment()

If the process crashes after charging the customer but before creating the shipment, the system must know what already happened after it restarts.

An orchestrated saga can persist explicit state:

saga_id: saga-9481
order_id: order-4721
state: PAYMENT_COMPLETED
next_step: CREATE_SHIPMENT

A simplified database representation could be:

CREATE TABLE sagas (
    saga_id VARCHAR(100) PRIMARY KEY,
    order_id VARCHAR(100) NOT NULL,
    state VARCHAR(50) NOT NULL,
    updated_at TIMESTAMP NOT NULL
);

After a restart, a worker can reload incomplete sagas and continue processing.

Useful states might include:

STARTED
ORDER_CREATED
INVENTORY_RESERVED
PAYMENT_COMPLETED
SHIPMENT_CREATED
COMPLETED

COMPENSATING
PAYMENT_REFUNDED
INVENTORY_RELEASED
CANCELLED

FAILED_MANUAL_REVIEW

Persisted workflow state turns the saga from a chain of transient function calls into a recoverable state machine.

Sagas and Eventual Consistency

Sagas do not provide atomic visibility across participating services.

During checkout, one service might report:

Order = PENDING

while another already reports:

Inventory = RESERVED

and the Payment service still reports:

Payment = NOT_STARTED

This is an example of temporary inconsistency.

If the workflow completes successfully, the services eventually converge on the intended business state. If the workflow fails, compensation moves them toward an alternative valid state.

The broader model is described in What Is Eventual Consistency?.

Applications built around sagas therefore need meaningful intermediate states. A UI may show:

Order received
Payment processing
Preparing shipment
Order confirmed

rather than pretending that the entire distributed operation happened instantaneously.

Idempotency and Retries

Retries are unavoidable in distributed workflows.

Suppose the orchestrator sends:

ChargePayment(order-4721)

The Payment service processes the charge successfully, but its response is lost.

Orchestrator          Payment Service

Charge ──────────────→ Charge succeeds
       ←──── response lost ──── X

The orchestrator cannot safely assume that the payment failed.

If it retries blindly:

ChargePayment(order-4721)
ChargePayment(order-4721)

the customer could be charged twice.

Saga steps and compensations should therefore usually be idempotent.

A payment request might include an idempotency key:

{
  "order_id": "order-4721",
  "amount": 14900,
  "currency": "USD",
  "idempotency_key": "saga-9481:charge-payment"
}

The Payment service stores the key with the result of the operation. A duplicate request returns the existing result instead of performing another charge.

The same principle should apply to compensations:

refund-payment:saga-9481
release-inventory:saga-9481

This is closely related to Idempotency and Deduplication in Distributed Systems.

Sagas and the Transactional Outbox

Event-driven sagas introduce another reliability problem.

Suppose the Inventory service performs:

1. Reserve inventory in database
2. Publish InventoryReserved event

The database commit can succeed while event publication fails.

Database commit ✓
       ↓
Process crashes
       ↓
Event publish ✗

Inventory is now reserved, but the next saga participant may never know.

The opposite ordering is also dangerous:

Publish event ✓
       ↓
Database commit ✗

The system announced an operation that never committed.

The Transactional Outbox Pattern for Reliable Messaging addresses this by storing the business change and an outgoing event record in the same local database transaction.

BEGIN;

UPDATE inventory
SET reserved = reserved + 1
WHERE product_id = 8472;

INSERT INTO outbox (
    event_type,
    aggregate_id,
    payload
)
VALUES (
    'inventory.reserved',
    'order-4721',
    '{...}'
);

COMMIT;

A separate publisher then reliably sends pending outbox records to the message broker.

Sagas and the transactional outbox solve different problems but are frequently used together:

Saga
  ↓
Coordinates business workflow

Transactional Outbox
  ↓
Reliably publishes committed state changes

Saga vs Two-Phase Commit

Sagas and two-phase commit address distributed transactions differently.

Property Saga Two-Phase Commit
Transaction model Multiple local transactions One coordinated distributed transaction
Rollback Business compensation Transaction rollback before commit
Resource locking Usually short local locks Resources may remain prepared while coordinating
Intermediate states Visible Generally hidden until final outcome
Consistency model Often eventual Atomic commit
Application complexity Compensation and workflow logic Coordinator and participant protocol

A saga gives up global atomic rollback in exchange for independently committed local operations and application-defined recovery.

This makes saga workflows especially relevant when services own independent databases or interact with external systems that cannot participate in one distributed transaction.

A deeper comparison is available in Designing Distributed Transactions with Sagas and Two-Phase Commit.

When to Use the Saga Pattern

A saga is useful when one business operation spans multiple independently managed transactional boundaries.

Typical examples include:

  • e-commerce checkout;
  • travel booking;
  • payment and fulfillment workflows;
  • account provisioning across services;
  • subscription activation;
  • multi-step resource allocation;
  • order cancellation and refund workflows.

A saga may be unnecessary when all required changes live in the same database and can safely use one local transaction.

Same database
     ↓
Local transaction available
     ↓
Prefer the simpler transaction

Splitting a straightforward database transaction into asynchronous saga steps adds failure modes, intermediate states, retries, monitoring requirements, and compensation logic.

The pattern should solve an actual distributed transaction problem rather than being applied simply because an application uses microservices.

Production Design Example

Consider a travel booking service that must reserve a flight, hotel, and rental car.

The business workflow is:

Create Trip
    ↓
Reserve Flight
    ↓
Reserve Hotel
    ↓
Reserve Car
    ↓
Confirm Trip

The system uses an orchestrated saga with a durable saga_id.

The saga starts:

{
  "saga_id": "trip-saga-7318",
  "trip_id": "trip-5521",
  "state": "STARTED"
}

The orchestrator requests a flight reservation:

ReserveFlight
idempotency_key =
trip-saga-7318:reserve-flight

The Flight service succeeds.

Flight = RESERVED
Saga   = FLIGHT_RESERVED

The hotel reservation also succeeds:

Hotel = RESERVED
Saga  = HOTEL_RESERVED

The car reservation fails because no cars are available.

Car = FAILED

The orchestrator now enters compensation:

CAR_RESERVATION_FAILED
          ↓
Cancel Hotel
          ↓
Cancel Flight
          ↓
Cancel Trip

Each compensation is independently retried until it reaches a terminal result.

Suppose the hotel cancellation succeeds immediately but the Flight service is temporarily unavailable.

Cancel Hotel  ✓
Cancel Flight ✗ timeout

The saga must not simply mark the entire workflow as cancelled.

Its durable state might become:

saga_id: trip-saga-7318
state: COMPENSATING
hotel_compensation: COMPLETED
flight_compensation: RETRY_PENDING

A retry worker later executes the remaining compensation.

Retry Cancel Flight
        ↓
Flight cancellation succeeds
        ↓
Saga = COMPENSATED
Trip = CANCELLED

If the cancellation repeatedly fails beyond the automated recovery policy, the workflow can move to:

FAILED_MANUAL_REVIEW

rather than pretending that the system is consistent.

A production saga should expose metrics such as:

  • sagas started and completed;
  • sagas currently in progress;
  • step execution latency;
  • step retry counts;
  • compensations started and completed;
  • compensation failures;
  • sagas stuck beyond expected duration;
  • workflows requiring manual intervention.

Tracing should also preserve the saga ID across services so that the complete workflow can be reconstructed during an incident.

The important design principle is that both forward progress and compensation are durable workflows. Compensation cannot be treated as best-effort cleanup.

Common Saga Mistakes

  • Treating a saga like a database rollback. Completed local transactions remain committed and require explicit compensating operations.
  • Assuming compensation cannot fail. Refunds, cancellations, and resource releases are distributed operations with their own failure modes.
  • Making saga steps non-idempotent. Lost responses and retries can otherwise create duplicate charges, reservations, or refunds.
  • Keeping saga state only in memory. A process crash must not erase knowledge of completed steps.
  • Ignoring intermediate states. Other services and users may observe a workflow while it is still executing or compensating.
  • Publishing events unreliably after database commits. This can leave the workflow permanently stuck.
  • Creating long choreography chains without clear ownership. The business workflow can become difficult to understand and operate.
  • Retrying permanent business failures forever. "No inventory available" is different from a temporary network timeout.
  • Assuming every operation can be perfectly compensated. Some actions are irreversible or can only be counteracted approximately.
  • Missing operational visibility. Production systems need to identify stuck, failed, and compensating sagas quickly.

Frequently Asked Questions

The Saga pattern changes several assumptions that are common when working with traditional database transactions.

Does a Saga Provide ACID Transactions?

Each individual saga step can use an ACID transaction inside its local service, but the saga as a whole is not one global ACID transaction.

Other services may observe intermediate states while the saga is progressing. Consistency is achieved through forward steps, compensation, and application-defined business rules.

Does Compensation Mean Database Rollback?

No. A compensating transaction is a new business operation.

If a payment was already charged, compensation may issue a refund. The original charge remains part of the transaction history.

Charge $100
    ↓
Saga fails
    ↓
Refund $100

The final financial effect may be reversed, but the original transaction was not technically rolled back.

What If a Compensating Transaction Fails?

Compensations should be treated as durable operations that can be retried. They should normally be idempotent so repeated attempts do not produce additional side effects.

If automated retries cannot complete the compensation, the saga may need to enter a failed state and trigger an alert or manual recovery process.

Should Every Microservice Workflow Use a Saga?

No. A saga is appropriate when one business transaction genuinely spans multiple independently committed systems and requires coordinated recovery.

If the operation can remain inside one service and one database transaction, that simpler boundary is usually easier to implement, reason about, and operate.

Conclusion

The Saga pattern manages distributed business transactions as a sequence of local transactions rather than one global atomic transaction. Successful steps move the workflow forward, while compensating transactions handle previously committed work when later steps fail.

Sagas can be coordinated through choreography or orchestration, but both approaches require careful handling of retries, idempotency, durable workflow state, intermediate consistency, and failed compensation.

The core principle is: a saga does not make a distributed transaction atomic; it makes partial progress and recovery explicit parts of the business workflow.

Author

Enjoyed this article?

Support Oleksandr Andrushchenko

Buy me a coffee

This helps Oleksandr Andrushchenko continue creating useful content

Related articles

Comments (0)