The Problem
In a microservices app, one user action often touches several services.
Take an e-commerce order:
Order Service → Payment Service → Inventory Service
Each service has its own database. Now what if the payment succeeds but the item is out of stock? We can't just call ROLLBACK, because no single transaction spans three databases.
How do we undo things properly when one step fails?
Option A: One Big ACID Transaction
A relational database gives us ACID guarantees, and a single transaction can roll everything back. But this only works inside one database.
- Microservices own separate databases, so one transaction can't cover them all.
- Sharing one database between services couples them tightly.
- Rows stay locked for the whole flow, which hurts speed and scalability.
Option B: Two-Phase Commit (2PC)
A coordinator asks every service "can you commit?" (prepare phase), and only if all say yes does it say "commit" (commit phase).
- Resources stay locked while everyone waits, so it is slow.
- If the coordinator crashes mid-way, services are stuck.
- Many modern databases and message brokers don't support it.
It works, but it doesn't scale well for microservices.
Option C: Saga Pattern ✅
A saga splits the big transaction into a sequence of small local transactions, one per service. Each step has a matching compensating transaction that undoes it.
Happy path
Create Order → Deduct Payment → Reserve Stock → ✅ Done
Failure path (out of stock)
Create Order → Deduct Payment → Reserve Stock ❌
│
Cancel Order ← Refund Payment ◄┘
Payment was deducted but the item is out of stock? The saga triggers a refund and the order is cancelled. The customer gets the money back.
Two Ways to Run a Saga
| Style | How it works | Good for |
|---|---|---|
| Choreography | Services publish events and react to each other's events. No central boss. | Simple flows with few services |
| Orchestration | A central orchestrator tells each service what to do next, and what to undo on failure. | Complex flows, easier to track |
Trade-offs
- The system is eventually consistent, so for a moment the money may be deducted before the refund lands.
- Compensating actions must be idempotent, meaning safe to run more than once.
- More code, because you write a "undo" for every step.
TL;DR
Can't roll back across services? Do each step locally, and if something fails, run the undo steps for the ones already done.
Top comments (0)