Saga Pattern vs 2PC — Real Tradeoffs
Saga Pattern vs 2PC — Real Tradeoffs
Distributed transactions across microservices force a choice between strong consistency and availability. Two-phase commit (2PC) offers atomicity but is operationally fragile. Sagas trade atomicity for availability and scalability.
Two-Phase Commit (2PC)
2PC coordinates participants through a prepare and commit phase. If any participant fails during prepare, the coordinator aborts.
Strengths:
- Atomic commit across services.
- Simplifies reasoning about consistency.
Weaknesses:
- Blocking: participants hold locks while waiting for commit.
- Single coordinator becomes a critical dependency.
- Poor fit for cloud-native environments with transient failures.
Saga Pattern
A Saga is a sequence of local transactions, each with a compensating action.
Strengths:
- No distributed locks.
- Works across heterogeneous data stores.
- Scales better and tolerates partial failures.
Weaknesses:
- Eventual consistency is visible to clients.
- Complex compensation logic.
- Requires careful orchestration and observability.
Choosing Between Them
| Dimension | 2PC | Saga |
|---|---|---|
| Consistency | Strong | Eventual |
| Availability | Lower | Higher |
| Latency | Higher | Lower |
| Operational Complexity | Moderate | High |
| Failure Handling | Blocking | Compensating |
2PC is suitable for tightly coupled systems within a single datastore or when the infrastructure guarantees low failure rates. Sagas are superior for microservices that value availability and autonomy.
Orchestrated Saga Example (Spring Boot)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Service
public class OrderSaga {
private final OrderService orderService;
private final PaymentService paymentService;
private final ShippingService shippingService;
public OrderSaga(OrderService orderService,
PaymentService paymentService,
ShippingService shippingService) {
this.orderService = orderService;
this.paymentService = paymentService;
this.shippingService = shippingService;
}
@Transactional
public void placeOrder(PlaceOrderCommand command) {
Order order = orderService.createOrder(command);
try {
paymentService.capturePayment(order);
shippingService.reserveShipment(order);
} catch (Exception ex) {
shippingService.releaseShipment(order);
paymentService.refundPayment(order);
orderService.cancelOrder(order);
throw ex;
}
}
}
Implementation Details That Matter
- Outbox pattern: ensure events are published atomically with database updates.
- Idempotent compensations: compensations must be safe to run multiple times.
- Saga state: store saga state explicitly to enable recovery.
Failure Modes to Consider
- Compensations failing and requiring manual intervention.
- Orchestrator crashes between steps.
- Duplicate events leading to double compensation.
Summary
2PC provides strong consistency but is brittle and latency-heavy. Sagas fit microservices better but require explicit state management, idempotent compensations, and robust observability to handle failures safely.