3 min read

Saga Pattern

The Saga Pattern is a design pattern for managing data consistency across microservices in distributed transaction scenarios. A saga is a sequence of local transactions. Each local transaction updates the database and publishes a message or event to trigger the next local transaction in the saga.

1. The Problem: Distributed Transactions

In a monolithic application with a single database, you can use ACID transactions (Atomicity, Consistency, Isolation, Durability). If you update two tables and the second update fails, the database rolls back the entire transaction automatically.

In a microservices architecture, services have their own private databases. You cannot use a standard database transaction that spans multiple services.

  • Scenario: An e-commerce "Checkout" involves:
    1. Order Service: Create order.
    2. Inventory Service: Reserve items.
    3. Payment Service: Charge credit card.
  • Failure: If the Payment fails, you must undo the Inventory reservation and cancel the Order.

2. The Solution: Compensating Transactions

Since we can't rely on database rollbacks, we must implement Compensating Transactions.

  • If a step fails, the saga executes a series of compensating steps that undo the changes made by the preceding steps.

Example:

  • Transaction: ReserveInventory()
  • Compensation: ReleaseInventory()

3. Implementation Approaches

Choreography (Event-Based)

There is no central coordinator. Each service produces and listens to events to decide on an action.

  • Flow:
    1. Order Service creates order -> Emits OrderCreated.
    2. Inventory Service hears OrderCreated -> Reserves items -> Emits InventoryReserved.
    3. Payment Service hears InventoryReserved -> Charges card -> Emits PaymentProcessed.
  • Pros: Simple, loose coupling.
  • Cons: Hard to track the workflow (who listens to what?), cyclic dependencies risk.

Orchestration (Command-Based)

A central coordinator (Orchestrator) tells the participants what local transactions to execute.

  • Flow:
    1. Order Service (Orchestrator) tells Inventory Service: "Reserve items".
    2. Inventory Service replies: "Done".
    3. Order Service tells Payment Service: "Charge card".
  • Pros: Centralized logic, easy to understand the flow.
  • Cons: The orchestrator can become a "God Service" with too much logic.

4. Pros and Cons

Pros Cons
Data Consistency: Maintains consistency across services without distributed locking (2PC). Complexity: Much more complex to implement and debug than ACID transactions.
Performance: Better performance than Two-Phase Commit (2PC) because it avoids locking resources. Eventual Consistency: Data is not consistent immediately. Users might see "Pending" states.
Compensating Logic: Writing undo logic for every action is tedious and error-prone.

5. Two-Phase Commit (2PC)

Two-Phase Commit (2PC) is an alternative to Sagas for handling distributed transactions. It provides strong consistency (ACID) rather than eventual consistency.

How it Works

  1. Prepare Phase: The transaction coordinator asks all participants if they are ready to commit. Participants lock the necessary resources and vote "Yes" or "No".
  2. Commit Phase:
    • If all participants voted "Yes", the coordinator sends a "Commit" command.
    • If any participant voted "No" (or timed out), the coordinator sends a "Rollback" command.

Why Sagas are often preferred over 2PC

  • Blocking: In 2PC, resources are locked from the Prepare phase until the Commit phase completes. If the coordinator fails, resources remain locked. Sagas do not hold locks across services.
  • Performance: 2PC is chatty and slow. Sagas are asynchronous and non-blocking.
  • Scalability: 2PC scales poorly in distributed systems due to locking.

6. Compensating Transaction Pattern

A compensating transaction is an operation that undoes the effect of a previously committed transaction. It is the "undo" button for distributed systems.

Key Characteristics

  • Idempotent: It must be safe to retry the compensation multiple times. If the system crashes while compensating, it will try again upon restart.
  • Not a Perfect Undo: It might not restore the system to the exact previous state. For example, if you booked a seat and then cancelled it, the seat is free again, but the history shows a booking and a cancellation.

Handling Failures

What happens if the compensating transaction itself fails?

  • Retry: The standard approach is to retry indefinitely until it succeeds.
  • Manual Intervention: If it fails permanently (e.g., due to a bug), a human operator must intervene to fix the data consistency.

programming/microservices-architecture programming/event-driven-architecture programming/database-basics