Two-Phase Commit vs. Saga Pattern: distributed transactions in microservices
Why synchronous Two-Phase Commit (2PC) fails in cloud microservices, and how the asynchronous Saga pattern coordinates distributed transactions with compensating rollbacks.
In monolithic architectures with a single relational database, maintaining data consistency is trivial. The application wraps multi-table operations inside a standard ACID transaction (BEGIN … COMMIT / ROLLBACK).
However, in a modern microservices architecture where each service owns its private database (e.g. Order Service, Payment Service, and Inventory Service), a single user transaction spans multiple independent databases.
How do you guarantee that an order isn’t charged if inventory reservation fails?
TWO-PHASE COMMIT (2PC - Blocking Locks):
Coordinator ──[1. PREPARE (Hold Locks)]──▶ Order DB, Payment DB, Inventory DB
Coordinator ──[2. GLOBAL COMMIT]─────────▶ Order DB, Payment DB, Inventory DB
Hazard: Single point of failure; all database locks freeze if coordinator dies.
SAGA PATTERN (Asynchronous Local Transactions + Compensations):
Step 1: Order DB commits OrderCreated ➔ Emits event
Step 2: Payment DB commits PaymentProcessed ➔ Emits event
Step 3: Inventory Out of Stock! ➔ Triggers Compensating Rollbacks:
➔ Refund Payment Service
➔ Cancel Order in Order Service
1. Why Two-Phase Commit (2PC) Breaks at Scale
Two-Phase Commit is a synchronous, blocking consensus protocol:
- Phase 1 (Prepare): The coordinator contacts all participants. Each service verifies constraints, reserves resources, and acquires pessimistic row locks.
- Phase 2 (Commit): If all nodes vote “YES”, the coordinator tells them to permanently commit.
The Critical Flaws of 2PC:
- Lock Contention: Locked rows cannot be modified by any other request while waiting for Phase 2.
- Coordinator Crash Vulnerability: If the transaction coordinator crashes between Phase 1 and Phase 2, participant databases are left in an indeterminate state with locked rows.
- Latency Bottleneck: Transaction latency equals the slowest participating microservice + multiple network round trips.
2. The Saga Pattern Solution
The Saga Pattern structures a distributed business process as a sequence of independent local transactions:
- Each microservice performs its local database operation, commits immediately, and emits an event or message to trigger the next step.
- No global database locks are held.
Handling Failures with Compensating Transactions
Because local transactions commit immediately, a Saga cannot use a classic SQL ROLLBACK. Instead, if step $N$ fails, the Saga triggers Compensating Transactions—forward-recovery actions that semantically undo previous steps:
| Step | Forward Action | Compensating Transaction (Undo) |
|---|---|---|
| 1. Create Order | Set order status = PENDING | Set order status = CANCELLED |
| 2. Charge Payment | Charge $50 to credit card | Issue $50 refund to credit card |
| 3. Reserve Inventory | Decrement item stock by 1 | Increment item stock by 1 |
3. Orchestration vs. Choreography Sagas
| Type | Architecture | Best Use Case |
|---|---|---|
| Choreography | Services listen to event brokers (Kafka / RabbitMQ) and trigger their step without a central controller. | Simple 2–3 step workflows across loosely coupled services. |
| Orchestration | A dedicated state machine orchestrator (Temporal, AWS Step Functions) tells each service what to execute. | Complex enterprise multi-step business workflows. |
4. Production Architectural Guidance
- Make Compensating Transactions Idempotent: Due to network retries, a compensating transaction (like issuing a refund) may execute more than once. Always use idempotency keys.
- Design for Eventual Consistency: During a Saga execution, intermediate states are visible to other queries (e.g. an order in
PENDING_PAYMENTstate). Design frontend UI to handle asynchronous order status updates.
Test Your Understanding
Quick architectural scenario challenge based on this breakdown.
A 3rd-party payment gateway starts timing out with 504 errors during peak traffic.
Q1.How should a downstream dependency failure be handled to protect upstream service availability?
Comments –
Sign in to share your thoughts
Nothing yet. Be the first.