0
0
Microservicessystem_design~7 mins

Saga pattern for distributed transactions in Microservices - System Design Guide

Choose your learning style9 modes available
Problem Statement
When a business process spans multiple microservices, a failure in one service can leave the system in an inconsistent state because traditional database transactions cannot span services. This leads to partial updates and data corruption without a way to rollback changes across services.
Solution
The Saga pattern breaks a distributed transaction into a series of smaller local transactions in each service. Each local transaction publishes an event or message to trigger the next step. If a step fails, compensating transactions are executed to undo previous changes, ensuring eventual consistency without locking resources across services.
Architecture
┌─────────────┐      ┌─────────────┐      ┌─────────────┐
│ Service A   │─────▶│ Service B   │─────▶│ Service C   │
│ (Local Tx)  │      │ (Local Tx)  │      │ (Local Tx)  │
└──────┬──────┘      └──────┬──────┘      └──────┬──────┘
       │                   │                   │
       │                   │                   │
       ▼                   ▼                   ▼
Compensate A◀────────Compensate B◀────────Compensate C
(Undo Tx)              (Undo Tx)            (Undo Tx)

This diagram shows a sequence of local transactions across three services with forward events triggering the next step. If any step fails, compensating transactions are triggered in reverse order to undo changes.

Trade-offs
✓ Pros
Enables distributed transactions without locking resources across services.
Improves system availability by avoiding global locks and long transactions.
Supports eventual consistency with clear rollback mechanisms via compensations.
Fits well with event-driven microservices architectures.
✗ Cons
Increases complexity due to managing compensating transactions and failure scenarios.
Requires careful design to ensure compensations correctly undo previous steps.
Eventual consistency means temporary data inconsistency visible to users.
Use when business processes span multiple microservices and strong consistency with distributed locking is not feasible. Suitable for systems with medium to high transaction volumes where eventual consistency is acceptable.
Avoid when strict ACID transactions are required across services or when compensating transactions are impossible or too complex to implement.
Real World Examples
Amazon
Amazon uses the Saga pattern to manage order processing across inventory, payment, and shipping microservices, ensuring orders are either fully processed or properly compensated.
Uber
Uber applies Saga to coordinate ride booking steps across services like driver assignment, payment, and notifications, handling failures gracefully without locking.
Netflix
Netflix uses Saga to maintain consistency in user subscription and billing microservices, allowing independent service updates with compensations on failure.
Code Example
The before code tries to manually rollback on failure, which is error-prone and scattered. The after code uses a Saga class to coordinate steps and compensations cleanly, ensuring consistent rollback if any step fails.
Microservices
### Before: No Saga, naive distributed transaction
class OrderService:
    def create_order(self, order):
        inventory_result = InventoryService.reserve(order.items)
        if not inventory_result:
            return False
        payment_result = PaymentService.charge(order.payment_info)
        if not payment_result:
            InventoryService.release(order.items)  # manual rollback
            return False
        ShippingService.schedule(order)
        return True

### After: Saga pattern with compensating transactions
class OrderSaga:
    def execute(self, order):
        try:
            InventoryService.reserve(order.items)
            PaymentService.charge(order.payment_info)
            ShippingService.schedule(order)
        except Exception as e:
            self.compensate(order)
            raise e

    def compensate(self, order):
        ShippingService.cancel(order)
        PaymentService.refund(order.payment_info)
        InventoryService.release(order.items)
OutputSuccess
Alternatives
Two-Phase Commit (2PC)
2PC uses a coordinator to lock resources and commit or rollback all services atomically, blocking resources during the transaction.
Use when: Choose 2PC when strict atomicity and consistency are mandatory and the system can tolerate blocking and lower availability.
Eventual Consistency with Event Sourcing
Event sourcing stores all changes as events and rebuilds state from them, focusing on immutable logs rather than compensations.
Use when: Choose event sourcing when auditability and replayability of state changes are priorities and eventual consistency is acceptable.
Summary
Saga pattern manages distributed transactions by splitting them into local transactions with compensations.
It avoids locking resources across services and supports eventual consistency.
Compensating transactions undo previous steps if a failure occurs, ensuring system consistency.