Microservices achieve organizational scalability by decoupling service boundaries. However, this decoupling introduces the most difficult problem in distributed systems: Cross-Service Consistency.
In a monolith, a transaction either commits or rolls back across the entire database. In microservices, each service has its own database. If a business process spans three services (e.g., Order → Payment → Inventory), you cannot use a global lock without destroying availability and performance.
A Saga is a sequence of local transactions. Each local transaction updates the database and publishes an event to trigger the next local transaction. If a step fails, the Saga executes compensating transactions to undo the preceding steps.
Services exchange events without a central coordinator.
OrderCreated → Payment Service (Success) → PaymentAuthorized → Inventory Service.A central "Saga Orchestrator" manages the state machine and tells each service what to do.
AuthorizePayment → Payment Service (Success) → Orchestrator → ReserveInventory → Inventory Service.A travel booking requires a Hotel and a Flight. If the Flight fails, the Hotel must be cancelled.
| Step | Service | Transaction | Compensation |
|---|---|---|---|
| 1 | Hotel | bookHotel() | cancelHotel() |
| 2 | Flight | bookFlight() | cancelFlight() |
| 3 | Payment | chargeCard() | refundCard() |
Orchestrator Logic (Pseudo-code):
def travel_saga(request):
try:
hotel_id = hotel_service.book(request)
try:
flight_id = flight_service.book(request)
try:
payment_service.charge(request)
except PaymentError:
flight_service.cancel(flight_id)
hotel_service.cancel(hotel_id)
except FlightError:
hotel_service.cancel(hotel_id)
except HotelError:
return "Booking Failed"
Sagas lack the "Isolation" of ACID. While a Saga is running, other transactions might see the "Intermediate State" (e.g., the Hotel is booked but the Flight isn't yet).
Mitigation Strategies:
application-level lock (e.g., set status = PENDING) to prevent other processes from modifying the same data.When a Saga spans 10 services, finding the point of failure is impossible without Distributed Tracing.
trace_id and span_id.trace_id to allow reconstructing the "Story" of a failed transaction across the entire cluster.