What is the Saga pattern and how does it manage distributed transactions?
Learn the Saga pattern for distributed transactions in microservices: compensating transactions, orchestration vs choreography, examples and interview answers.
Expected Interview Answer
The Saga pattern manages a distributed transaction as a sequence of local transactions across services, where each step publishes an event that triggers the next, and any failure runs compensating transactions to undo the completed steps instead of a single atomic rollback.
Because a database-style two-phase commit does not scale across independently owned services, a saga trades ACID atomicity for eventual consistency. Each service commits its own local transaction and, if a later step fails, previously successful steps are reversed by explicit compensating actions (for example, refunding a payment or releasing reserved stock). Sagas come in two coordination styles — orchestration, where a central coordinator directs each step, and choreography, where services react to each other's events.
- Avoids distributed locks and two-phase commit
- Each service keeps ownership of its own data
- Scales across independently deployed services
- Provides a clear failure-recovery path via compensation
- Enables loose coupling and asynchronous processing
AI Mentor Explanation
A saga is like a run chase built one over at a time rather than in a single swing. Each over is a local commit — runs scored are booked — and there is no single umpire freezing the whole match to undo everything at once. If a collapse happens, the team does not erase past overs; it plays compensating overs, shifting the batting order and tactics to recover, exactly as compensating transactions repair earlier committed steps.
Step-by-Step Explanation
Step 1
Model the business flow as local steps
Break the distributed transaction into a sequence of local transactions, one per service (create order, reserve stock, take payment, arrange shipping).
Step 2
Define a compensating action per step
For every forward step, author its reverse (cancel order, release stock, refund payment) so any partial progress can be undone.
Step 3
Choose a coordination style
Pick orchestration (a central coordinator invokes each step) or choreography (services react to each other's events).
Step 4
Propagate events or commands
On each successful local commit, emit an event or send the next command so the saga advances to the following step.
Step 5
Trigger compensation on failure
When a step fails, run the compensating transactions of all previously completed steps in reverse order to restore a consistent state.
Step 6
Make steps idempotent and durable
Persist saga state and design steps to tolerate retries so a redelivered message does not double-apply an action.
What Interviewer Expects
- Why two-phase commit does not scale across microservices
- The trade of atomicity for eventual consistency
- The concept and design of compensating transactions
- Awareness of orchestration versus choreography
- The need for idempotency and durable saga state
Common Mistakes
- Assuming a saga gives full ACID atomicity like a single database
- Forgetting to author a compensating action for every forward step
- Ignoring idempotency, causing duplicate effects on retries
- Confusing a saga with distributed two-phase commit
- Not persisting saga state, so failures cannot be recovered
Best Answer (HR Friendly)
“A saga breaks one big transaction that spans several services into a chain of smaller steps, each doing its own part. If a later step fails, the system runs undo actions for the steps that already succeeded instead of rolling everything back at once, which keeps the data consistent without locking all the services together.”
Code Example
async function placeOrderSaga(order) {
const completed = []
try {
await orderService.create(order)
completed.push(() => orderService.cancel(order.id))
await inventoryService.reserve(order)
completed.push(() => inventoryService.release(order))
await paymentService.charge(order)
completed.push(() => paymentService.refund(order))
await shippingService.dispatch(order)
} catch (err) {
// run compensations in reverse order
for (const compensate of completed.reverse()) {
await compensate()
}
throw err
}
}Follow-up Questions
- How do compensating transactions differ from a database rollback?
- When would you choose orchestration over choreography for a saga?
- How do you guarantee idempotency across saga steps?
- What happens if a compensating transaction itself fails?
- How does the outbox pattern help make saga events reliable?
MCQ Practice
1. What does a saga run when a later step in the sequence fails?
Sagas have no global rollback; they undo completed steps by executing their compensating transactions.
2. Why is two-phase commit usually avoided in microservices?
Two-phase commit holds locks across services and creates a scalability and availability bottleneck, which sagas avoid.
3. What consistency model does the Saga pattern provide?
By committing local transactions and compensating on failure, sagas achieve eventual consistency rather than ACID atomicity.
Flash Cards
What is a saga? — A distributed transaction modeled as a sequence of local transactions, each with a compensating action for failure recovery.
What replaces rollback in a saga? — Compensating transactions that explicitly reverse the effects of previously committed steps.
Two saga coordination styles? — Orchestration (central coordinator) and choreography (services react to events).
Why must saga steps be idempotent? — Messages can be redelivered, so a step must produce the same result if executed more than once.