Cover image for Data Consistency in Microservices — Saga Pattern

Gouranga Das Samrat

One-liner: In microservices, you can't use a single database transaction across services — the Saga pattern breaks it into a sequence of local transactions with compensating rollbacks on failure.


❓ The Problem: Distributed Transactions

In a monolith, you can wrap everything in one DB transaction:

BEGIN;
  UPDATE orders SET status='confirmed' WHERE id=1;
  UPDATE inventory SET stock=stock-1 WHERE product_id=5;
  INSERT INTO payments (order_id, amount) VALUES (1, 99.99);
COMMIT; -- all or nothing

Enter fullscreen mode Exit fullscreen mode

In microservices, each service owns its own DB. You cannot do a cross-service ACID transaction. If the payment service fails after inventory was deducted, you're in an inconsistent state.


🎭 The Saga Pattern

A Saga is a sequence of local transactions. Each step publishes an event triggering the next. If any step fails, compensating transactions undo the previous steps.

Example: E-commerce Order Flow

Step 1: Order Service      → Create order (PENDING)
Step 2: Inventory Service  → Reserve items
Step 3: Payment Service    → Charge customer
Step 4: Shipping Service   → Schedule delivery
Step 5: Order Service      → Mark order CONFIRMED

Enter fullscreen mode Exit fullscreen mode

Happy path ✅:

Order Created → Items Reserved → Payment Charged → Shipping Scheduled → Done ✅

Enter fullscreen mode Exit fullscreen mode

Failure at Payment ❌:

Order Created → Items Reserved → Payment FAILED
                                       ↓
                          Compensate: Release inventory
                          Compensate: Cancel order

Enter fullscreen mode Exit fullscreen mode


🏗️ Two Implementation Styles

1. Choreography (Event-driven, decentralized)

Each service listens for events and reacts. No central coordinator.

OrderService    ──publishes──► "OrderCreated"
InventoryService ──listens──► reserves stock, publishes "StockReserved"
PaymentService  ──listens──► charges card, publishes "PaymentProcessed"
ShippingService ──listens──► schedules delivery, publishes "Shipped"

On failure:
PaymentService publishes "PaymentFailed"
InventoryService listens, releases stock, publishes "StockReleased"
OrderService listens, marks order CANCELLED

Enter fullscreen mode Exit fullscreen mode

Pros: Simple, decoupled, no single point of failure

Cons: Hard to track overall saga state, cyclic dependencies possible


2. Orchestration (Centralized coordinator)

A dedicated Saga Orchestrator tells each service what to do.

                    ┌─────────────────────┐
                    │   Saga Orchestrator  │
                    │   (Order Saga)       │
                    └──────────┬──────────┘
                               │
         ┌─────────────────────┼──────────────────────┐
         ▼                     ▼                       ▼
  InventoryService       PaymentService          ShippingService
  "Reserve stock"        "Charge $99"            "Ship to addr"
  → "Reserved" ✅        → "Failed" ❌           (not called)
         │                     │
         └─────────────────────┘
                    Orchestrator receives "Failed"
                    → tells InventoryService: "Release stock"
                    → marks saga FAILED

Enter fullscreen mode Exit fullscreen mode

Pros: Clear saga state, easy to audit and debug

Cons: Orchestrator is a bottleneck / SPOF if not designed carefully


🆚 Choreography vs Orchestration

Choreography Orchestration
Control Decentralized Centralized
Coupling Loose (events) Tighter (direct calls)
Visibility Hard to trace end-to-end Easy — one place to look
Complexity Low per-service, high overall High orchestrator logic
Best for 3-4 step sagas Complex, many-step flows

💾 Saga State Storage

Track saga state to survive crashes:

CREATE TABLE sagas (
  id UUID PRIMARY KEY,
  type VARCHAR(50),          -- 'order_saga'
  current_step VARCHAR(50),  -- 'awaiting_payment'
  status VARCHAR(20),        -- 'IN_PROGRESS', 'COMPLETED', 'FAILED'
  payload JSONB,             -- order details
  created_at TIMESTAMP,
  updated_at TIMESTAMP
);

Enter fullscreen mode Exit fullscreen mode


✅ Pros

  • Maintains data consistency without distributed transactions
  • Works with polyglot persistence (each service uses its own DB)
  • Choreography scales well horizontally
  • Orchestration provides clear audit trail

❌ Cons

  • Eventual consistency — data temporarily inconsistent between steps
  • Compensating transactions are complex to implement correctly
  • Hard to debug when something fails mid-saga
  • Idempotency required for every step (retries happen)

⚖️ When to Use / When NOT to Use

✅ Use when:

  • Multi-service workflows that must be atomic (order → payment → shipping)
  • Different services own different databases
  • Long-running business processes (insurance claims, loan approvals)

❌ Avoid when:

  • Single service with one DB (just use a DB transaction)
  • Simple async operations that don't need rollback
  • You can redesign to avoid the distributed write entirely