Distributed Transactions: Maintaining Consistency
Distributed Transactions: Maintaining Consistency
In a monolith, keeping data consistent is easy. You wrap your database calls in a single transaction, and the database ensures that either everything succeeds or everything fails (ACID). In microservices, every service has its own database. A single business process (like "Place Order") might involve the Order Service, Payment Service, and Inventory Service.
How do you ensure consistency across three different databases?
1. Two-Phase Commit (2PC)
A classic approach where a "Coordinator" asks all services to prepare for a commit, and then tells them all to commit if everyone is ready.
- Pros: Strong consistency.
- Cons: Extremely slow (high latency). If the coordinator or any service hangs, the whole system locks up. Not recommended for high-scale microservices.
2. The Saga Pattern
A Saga is a sequence of local transactions. Each service performs its own local transaction and then publishes an event to trigger the next service.
- Compensating Transactions: If one step fails, the Saga must trigger "undo" actions for all previous steps to return the system to a consistent state.
- Example: If 'Payment' fails, the 'Order' must be canceled and 'Inventory' must be returned.
Implementing a Choreography-Based Saga
- 1Step 1
The 'Order Service' creates an order in its local database with a status of
PENDINGand publishes anOrderCreatedevent to a message broker. - 2Step 2
The 'Payment Service' listens for
OrderCreated. it processes the payment locally and publishes aPaymentSuccessfulevent. - 3Step 3
The 'Inventory Service' listens for
PaymentSuccessful. It tries to reserve items but finds they are out of stock. It publishes anInventoryFailedevent. - 4Step 4
The 'Payment Service' listens for
InventoryFailed. It immediately executes a Refund transaction and publishes aPaymentRefundedevent. - 5Step 5
The 'Order Service' listens for
PaymentRefundedand updates the order status toCANCELED. The system is now consistent, even though the order failed.
Saga Variations
- Choreography: Services talk to each other via events. No central controller. (Decoupled, but hard to track the overall state).
- Orchestration: A central "Saga Orchestrator" tells each service what to do and handles the failures. (Easier to manage, but the orchestrator is a new point of failure/complexity).
Eventual Consistency
Sagas do not provide ACID consistency; they provide Eventual Consistency. For a few seconds, the order might be 'Pending' while the inventory is being checked. You must design your UI and business logic to handle these transient states.
Common Mistakes
- Forgetting Compensation: Implementing the "happy path" but not the "undo" path. This leads to orphaned records (e.g., a payment was taken but no order exists).
- Circular Dependencies: Designing Sagas where Service A triggers B, which triggers C, which triggers A. This creates infinite loops.
- Lack of Idempotency: If an event is delivered twice, the compensating transaction (like a refund) must not happen twice.
Recap
- 2PC is for strong consistency but doesn't scale.
- Sagas use local transactions and Compensating Actions to maintain eventual consistency.
- Choreography is decentralized; Orchestration is centralized.
- Idempotency is critical for both the main and compensating transactions.
Knowledge Check
"What is a 'Compensating Transaction' in the Saga pattern?"