Saga Pattern
Managing transactions that span services without distributed locks — local transactions chained together, compensating transactions for rollback, and orchestration vs choreography.
A saga maintains data consistency across multiple services without a distributed ACID transaction. It runs a sequence of local transactions, one per service; if any step fails, it executes compensating transactions to undo the completed ones. It trades atomic, immediate consistency for availability and eventual consistency — the realistic way to coordinate business operations across service boundaries.
A single business operation (place an order) often spans multiple services, each with its own database. You can't wrap them in one ACID transaction across service boundaries, and two-phase commit (2PC) is slow, locks resources, and couples services. The saga pattern provides consistency without a distributed transaction.
A saga breaks the operation into a series of local transactions, one per service. Each step commits in its own database and then triggers the next. There's no global lock — each service does its part and moves on, so the system stays available.
If a step fails partway through, there's no automatic rollback — instead the saga runs compensating transactions to semantically undo the already-committed steps, in reverse order (refund the payment, release the inventory). The system reaches a consistent end state through forward steps or compensations.
Orchestration uses a central coordinator that explicitly tells each service what to do and handles compensation — easy to follow and monitor, but a central component. Choreography has services react to each other's events with no coordinator — loosely coupled, but the overall flow is implicit and harder to trace.
Key Concepts
A sequence of local transactions across services that achieves consistency without a distributed ACID transaction.
An action that semantically undoes a completed step (refund, release) when a later step fails.
A central coordinator drives the steps and compensations — explicit, observable, but a focal component.
Services react to each other's events with no coordinator — decoupled, but the flow is implicit.
The system converges to a consistent state through steps or compensations, not instant atomic rollback.
Undoing the business effect (not a DB rollback) — and noting some actions can't be perfectly undone.
1// ORCHESTRATION — a central coordinator drives the steps and compensations.2class PlaceOrderSaga {3 async execute(order) {4 const done = [];5 try {6 await payments.reserve(order); done.push("payment");7 await inventory.reserve(order); done.push("inventory");8 await shipping.arrange(order); done.push("shipping");9 return "committed";10 } catch (err) {11 // failure → undo completed steps in REVERSE order12 if (done.includes("inventory")) await inventory.release(order);13 if (done.includes("payment")) await payments.refund(order);14 return "rolled back"; // eventual consistency, not ACID15 }16 }17}1819// CHOREOGRAPHY — no coordinator; services react to each other's events.20// payments --OrderPaid--> inventory21// inventory --StockReserved--> shipping22// shipping --ShipFailed--> inventory(release) --> payments(refund)
Cross-service transactions are unavoidable in microservices, and sagas are the standard answer — making them a frequent senior design-interview topic. Understanding compensations, the orchestration/choreography trade-off, and the reality of eventual consistency is essential for building correct distributed workflows that don't leave money charged for orders that never ship.
Common Pitfalls
1Placing an Order Across Payment, Inventory & Shipping
Checkout must charge the card (Payments), reserve stock (Inventory), and book a courier (Shipping) — three separate services and databases. If shipping can't be arranged, the charge and stock reservation must not stand.
There's no single transaction spanning the three services, so a failure at shipping would otherwise leave the customer charged and stock wrongly reserved — partial, inconsistent state.
Model it as a saga. Each service commits its local step in order; if shipping fails, the saga runs compensations in reverse — release the inventory, then refund the payment. The order ends either fully placed or fully unwound, with no partial state.
Takeaway: When one operation spans multiple services, a saga gives you all-or-nothing semantics through compensating transactions instead of an impossible distributed ACID transaction — accepting eventual consistency along the way.
2Choosing Orchestration Over Choreography as It Grew
A saga implemented with pure choreography (services reacting to events) grew to 8 steps. Nobody could answer 'what's the current state of this order?' or trace why a saga got stuck, and circular event chains crept in.
With choreography, the workflow is implicit in scattered event handlers. As steps multiplied, the emergent flow became impossible to reason about, monitor, or debug, and compensation logic was spread everywhere.
Introduce an orchestrator (e.g. a state machine / workflow engine) that owns the saga: it issues each command, tracks the current step, and triggers compensations centrally. The flow becomes explicit, observable, and testable, while services stay simple command handlers.
Takeaway: Choreography suits simple, few-step flows with maximum decoupling; orchestration shines as workflows grow complex, giving you one place to see state, handle compensation, and debug. Pick per saga, not globally.
Join the discussion
Comments
Loading comments...
Cookie preferences
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.