Saga Pattern
matureSummary
Run a business transaction that spans multiple services as a sequence of local transactions, each atomic in its own service and each paired with a compensating transaction that semantically reverses it if a later step fails. It trades isolation for service autonomy: concurrent sagas can observe each other's intermediate state.
Problem
Business operations that span multiple microservices (reserve inventory, charge payment, create shipment) cannot be wrapped in one ACID transaction. Without coordination, a partial failure leaves the system inconsistent with no defined recovery path.
Description
A transaction spanning multiple services cannot be wrapped in one ACID transaction without 2PC, which is blocking and impractical across heterogeneous, independently owned services. A saga instead models the work as a sequence of local transactions T1, T2, ... Tn, each atomic within its own service. If step Tk fails, the saga runs compensating transactions C(k-1), C(k-2), ... C1 in reverse to undo the steps that already committed.
A saga gives ACD, not ACID. It provides atomicity in the sense that the sequence eventually either fully completes or is fully compensated, consistency, and durability, but it gives up isolation. That missing I is the whole design tension. Because each Ti commits on its own, there is no boundary hiding the half-finished saga from anyone else.
Lack of isolation and observable intermediate states. While a saga is mid-flight, its committed-but-not-yet-final steps are visible to concurrent transactions. Another saga can read state left by T1 before Tn runs or before compensation reverses it, which produces business-level anomalies: a dirty read of state that will be compensated away, or a lost update when two sagas touch the same record. The database will not prevent this, so the application must.
Countermeasures live in the application, not the engine. The common one is a semantic lock: a saga marks the records it is working on with an in-progress or pending flag, and other transactions treat a flagged record specially (wait, reject, or handle it as tentative) rather than acting on half-done state. Others include commutative updates, designing steps so their order does not change the result (increment and decrement rather than set), and reread-or-version checks that detect a concurrent modification before committing. These reintroduce, by hand, the isolation the saga gave up.
Choreography failure modes. In a choreographed saga each service publishes an event on completion and subscribes to others' events to know when to act; there is no coordinator, and the transaction emerges from the event flow. It is simple to deploy but the saga's state is implicit, scattered across topics, so understanding or debugging a stuck saga means correlating events across services, and cyclic event dependencies appear beyond a few steps. It suits sagas of 2 to 4 steps.
Orchestration failure modes. In an orchestrated saga a central orchestrator, usually a state machine persisted in PostgreSQL, commands each participant and issues compensations on failure. It gives one place to observe and audit saga state, which suits 5-plus steps and complex error handling, but the orchestrator is a single point of failure and potential bottleneck: it must persist its state durably and deliver commands at-least-once so a restart resumes in-flight sagas rather than losing them.
Compensation is not rollback. A committed step really happened and may have had external side effects (an email sent, a card charged). Compensation does not erase it; it issues a new forward transaction that semantically reverses it (a refund, not an un-charge). That compensation can itself fail, which needs a dead-letter path and manual intervention, and some effects cannot be reversed at all. Because delivery is at-least-once, every step and every compensation must be idempotent, or a redelivery will double-charge or double-allocate.
Tradeoffs
Provides a structured recovery path for multi-service transactions
Each service owns its local transaction; no cross-service locking
No isolation between concurrent sagas; intermediate state is observable unless the app adds countermeasures
Choreography requires event correlation; orchestration adds a stateful coordinator
Compensation failures are a second-order failure mode requiring explicit handling
Orchestration gives a single audit trail; choreography requires correlation
When to use
A business transaction spans multiple services or data stores
2PC across services is blocking and impractical; Saga provides a structured eventual-consistency model with defined compensation.
Services are independently deployable and owned by different teams
Synchronous cross-service coupling (2PC, distributed locks) couples availability and deployment; Saga decouples via events or async commands.
A compensating transaction can be defined for every forward step
Saga is only viable if each step has a defined compensation; non-compensable steps (a physical shipment) need a different approach, such as ordering them last.
Eventual consistency at the business-transaction level is acceptable
Saga provides no isolation between concurrent sagas; intermediate state is observable unless the application adds semantic locks or similar countermeasures.
When not to use
Steps cannot be compensated (e.g., external side effects that cannot be reversed)
If a step cannot be reliably reversed, Saga cannot restore consistency; hold that step until all others are certain, or choose another approach.
Strong isolation between concurrent transactions is required
Saga provides no isolation; concurrent sagas can read each other's intermediate state. Serializable isolation within one database, or 2PC, is needed instead.
The transaction spans only a single service and database
A local ACID transaction is simpler, faster, and strictly stronger.
Operational Requirements
Define and implement a compensating transaction for every forward step before deployment
Compensation cannot be added retroactively; a missing compensation means permanent inconsistency when its step fails.
Make every step and every compensation idempotent
At-least-once delivery means steps run more than once; non-idempotent steps double-charge, double-allocate, or create duplicates.
Apply an isolation countermeasure (semantic lock or equivalent) for records touched mid-saga
Without a semantic lock or commutative design, concurrent sagas read each other's intermediate state; flag in-progress records so others handle them as tentative.
Instrument saga state transitions and dead-letter compensation failures
A saga stuck in COMPENSATING is inconsistent business state; compensation failures must alert immediately for manual resolution.
Persist orchestrator state durably (PostgreSQL) with at-least-once command delivery
An orchestrator restart must resume in-flight sagas; in-memory state loses every in-progress transaction on restart.
Characteristics
Technologies
Canonical
Alternatives
Relationships
Evolves from
Complements
Conflicts with
Basis
Well-established pattern in microservice architectures; the isolation gap and its application-level countermeasures, and compensation failure as a second-order risk, are documented in the saga literature and in production incident reports.
Related Architecture Knowledge
Outbound: this entity affects
Saga replaces two-phase commit with a sequence of local transactions and compensating transactions, eliminating the blocking distributed lock problem of 2PC at the cost of eventual consistency and more complex failure handling.
Tradeoffs
- ·Saga does not provide atomicity: intermediate states are visible between steps
- ·Compensating transactions add implementation complexity: every step needs a corresponding undo operation
- ·{'Eventual consistency': 'a saga rollback takes time: the system is briefly inconsistent during compensation'}
Inbound: affects this entity
Temporal provides durable workflow execution with compensation support, implementing the saga pattern without custom state machine code in the application.
Full relationship →Used In Architecture Scenarios
Marketplace Platform
An e-commerce order lifecycle platform handling cart, checkout, payment, fulfillment, and returns across a mixed read/write workload where product discovery is read-heavy, checkout is write-transactional, and fulfillment is event-driven. The saga pattern orchestrates multi-step checkout: reserve inventory → charge payment → confirm order → notify fulfillment. PostgreSQL owns order records and inventory with row-level locking; Redis holds session state and cart contents with sub-millisecond access; RabbitMQ delivers fulfillment notifications with dead-letter handling; Elasticsearch serves product search and order history with faceted navigation. CQRS separates the write command path from the read model: the order read model is denormalized for fast order history queries without joining across domain tables.
Marketplace Platform
A two-sided marketplace architecture serving buyers, sellers, listings, transactions, search, and notifications from a shared infrastructure, where multiple independent domains must coordinate without tight coupling. Event sourcing captures every state transition; the saga pattern orchestrates multi-step transactions (create order, reserve inventory, charge payment, notify seller) with compensating transactions for partial failures. Kafka decouples domain event publication from consumption; RabbitMQ handles notification fanout; Elasticsearch serves listing search; Redis caches listing display and session state.