What is Saga pattern and when to use it
In a monolith, an ACID transaction guarantees that all changes are either committed or rolled back together. In microservices, data is spread across different databases, and the...
Junior Level
Saga is a pattern for managing distributed transactions in microservices.
In a monolith, an ACID transaction guarantees that all changes are either committed or rolled back together. In microservices, data is spread across different databases, and there is no mechanism that can atomically change everything at once.
In a monolith there is one database and regular transactions (ACID). In microservices, each service has its own database, and you cannot make a single transaction across multiple services.
Saga solves this problem:
- Breaks a large transaction into a sequence of local transactions
- Each local transaction runs in its own service
- If something goes wrong — compensating transactions (rollback) are executed
Example — online store order:
1. Order Service: create order
2. Payment Service: charge money
3. Inventory Service: reserve product
4. Shipping Service: ship order
If Payment fails -> cancel order (compensation)
If Inventory fails -> refund money + cancel order
// What the user sees: order created -> payment processed -> inventory reserved.
// If Shipping fails: compensation cancels everything, user sees "order cancelled".
// Eventual consistency typically takes seconds to minutes.
Middle Level
Two types of Saga
1. Choreography:
Each service publishes an event -> other services listen and react
Order Service -> "OrderCreated" -> Payment Service
Payment Service -> "PaymentCompleted" -> Inventory Service
Inventory Service -> "InventoryReserved" -> Shipping Service
2. Orchestration:
A central orchestrator manages the entire process
Orchestrator
/ | \ \
Order Payment Inventory Shipping
When to use
Saga is suitable when:
- A transaction spans multiple services
- Eventual consistency is acceptable (not immediate consistency)
- There is no single database
If business requirements legally require strict ACID (bank transfers), Saga may not be suitable without additional guarantees (locks, two-phase commit).
Common mistakes
- Missing compensation:
Order -> Payment -> Inventory (fail) Need: refund(payment) + cancel(order) If refund is forgotten -> money is charged, no order exists
Senior Level
Internal Implementation
Saga log:
Each Saga must log:
1. Started steps
2. Completed steps
3. Compensations
This allows state recovery after a restart
Architectural Trade-offs
| Choreography | Orchestration |
|---|---|
| No single point of failure | Orchestrator — single point of failure |
| Hard to trace the flow | Easy to monitor |
| Cyclic dependencies | No cyclic dependencies |
| Good for simple Sagas | Good for complex Sagas |
Edge Cases
1. Idempotency:
A compensating transaction may be called twice
-> Idempotency is required
refund(paymentId):
if alreadyRefunded(paymentId): return
doRefund(paymentId)
markRefunded(paymentId)
2. Partial failure:
Saga: A -> B -> C (fail)
Compensations: compensate(B) -> compensate(A)
If compensate(B) also fails -> retry + alert
Production Experience
Order Saga:
@SagaDefinition
public class OrderSaga {
@StartSaga
@SagaEventHandler(associationProperty = "orderId")
public void handle(OrderCreatedEvent event) {
SagaLifecycle.start();
commandGateway.send(new ReserveInventoryCommand(event.orderId()));
}
@SagaEventHandler(associationProperty = "orderId")
public void handle(InventoryReservedEvent event) {
commandGateway.send(new ProcessPaymentCommand(event.orderId()));
}
@SagaEventHandler(associationProperty = "orderId")
public void handle(PaymentCompletedEvent event) {
commandGateway.send(new ShipOrderCommand(event.orderId()));
SagaLifecycle.end();
}
@SagaEventHandler(associationProperty = "orderId")
public void handle(InventoryReservationFailedEvent event) {
// Compensation — cancel order
commandGateway.send(new CancelOrderCommand(event.orderId()));
SagaLifecycle.end();
}
}
Best Practices
✅ Log every Saga step
✅ Implement idempotency for compensations
✅ Use timeouts for each step
✅ Monitor latency between steps
❌ Don't use for simple CRUD operations
❌ Don't forget compensating transactions
❌ Don't ignore partial failures
Interview Cheat Sheet
Must know:
- Saga — pattern for distributed transactions without a single database
- Two types: Choreography (event-driven) and Orchestration (coordinator-driven)
- Compensating transactions undo steps in reverse order
- Eventual consistency — data is not consistent instantly
- Idempotency is critical for compensations
- Transactional Outbox guarantees event delivery
- Choreography — 2-3 services, Orchestration — 5+ services
- Saga timeout protects against hung transactions
Common follow-up questions:
- How does Saga differ from 2PC? Saga does not block resources, works with heterogeneous databases, but provides eventual consistency instead of strict ACID.
- What happens if a compensation also fails? Retry + alert for manual intervention.
- When NOT to use Saga? For simple CRUD operations in a single service or when strict ACID is required (bank transfers).
- How to monitor Sagas? Log every step, track duration between steps, use distributed tracing.
Red flags (DO NOT say):
- “Saga guarantees ACID” — no, eventual consistency
- “Compensation = ROLLBACK” — no, it’s a new transaction
- “Choreography is better for 10 services” — no, orchestration is
- “Saga is not needed if you have Kafka” — Kafka is only transport, logic is still needed
Related topics:
- [[2. What is the difference between choreography and orchestration in Saga]]
- [[3. How to implement distributed transactions in microservices]]
- [[4. What are compensating transactions]]
- [[13. What is Database per Service pattern]]
- [[22. What is distributed tracing]]