Post

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

Dimension2PCSaga
ConsistencyStrongEventual
AvailabilityLowerHigher
LatencyHigherLower
Operational ComplexityModerateHigh
Failure HandlingBlockingCompensating

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.

This post is licensed under CC BY 4.0 by the author.