What is the Saga pattern?


The Saga pattern is currently known as a way to manage transactions in distributed systems. The key idea is to break up a distributed transaction into local transactions executed by each service. Each service handles only its own state and data. In case of failure, compensating transactions are executed to undo the original transactions. Compensating transactions keep the whole system in a consistent state without the need for any distributed locking mechanism.

However, the original “sagas” were primarily designed for long-lived transactions in classic relational database systems, although the authors mention the concept is applicable to distributed database systems as well.

Brief history

The paper describing sagas was published by Hector Garcia-Molina and Kenneth Salem in 1987. They proposed a solution to deal with long-lived transactions — transactions that take a long time to execute and therefore hold the resources of the underlying database, in turn blocking other short-lived and more common transactions that are executed more often. The authors said that a long-lived transaction is a saga if it can be written as a sequence of transactions that can be interleaved with other unrelated transactions instead of blocking them. The underlying database system guarantees that either all transactions within a saga are executed or compensating transactions are run to amend the partial execution.

Not a great example, but imagine that instead of writing a single transaction for a money transfer (withdraw from the first account, deposit to the second account, commit or rollback), you write one transaction for the withdrawal, a second transaction for the deposit, and have compensating transactions for partial failure. The compensating transaction would do the opposite — deposit the money back to the first account and withdraw from the second. This is important — we can’t just remember the previous balance of each account and restore it in case of failure, because it could have been changed by other transactions!

If you’re interested in the history of the original saga notion, make sure you read the paper cited below.

Hector Garcia-Molina and Kenneth Salem. 1987. Sagas. SIGMOD Rec. 16, 3 (Dec. 1987), 249–259. https://doi.org/10.1145/38714.38742

Saga pattern in the service world

In our modern era, we can leverage the Saga pattern to implement distributed transactions. Let’s start with an example — in a simple e-commerce system, we have an order service, an inventory service, and a payment service. When a customer places an order, we must create it in the order service, reserve the products in the inventory service, and initiate a payment flow in the payment service.

Usually, every service has its own state and operates over its own database. Therefore, we have to coordinate the flow somehow so we don’t create orders without lowering the inventory, and so on. The core idea is still the same as for long-lived transactions in a single database system. If any part of the distributed transaction fails, we execute compensating transactions in each affected service. For example, if we create an order in the order service, lower the inventory in the inventory service, but fail to initiate the payment in the payment service, we can either remove or cancel the created order and restore the inventory for each product (again, not returning the inventory to its previous value — it could have been changed by other orders).

Now, the question is — how do we know when to run the compensating transactions?

Orchestration and choreography

These are the two common approaches in distributed systems. Simply put, orchestration requires a single orchestrator for a distributed transaction. Using the previous example, we could introduce an orchestrator service that drives the whole process of order creation. First, it creates an order in the order service and waits for a response. Once successfully created, it commands the inventory service to reduce the inventory for each product in the order. Finally, it initiates the payment through the payment service. In case of failure, the orchestrator knows how to execute the compensating operations in each service.

Choreography is often understood as an event-driven architecture. There is no single commander initiating operations. Instead, we emit an event whenever something happens in our distributed system. The customer placing an order can itself be an event. Our order service reacts to this event by creating the order. Once done, it emits an order created event. The inventory service listens for such events and lowers the inventory for the products in the order (whether the products are part of the event or the inventory service must first fetch the order from the order service is a discussion for another time). Once done, the inventory service emits an inventory reserved event — the payment service knows it’s now its turn to initiate a payment and emit a payment initiated event (our order service should probably listen for this). If anything fails, the appropriate event is emitted by the service where the failure occurred. Every service must act accordingly — for example, when payment initiation fails, the payment initiation failed event is emitted and both the order service and inventory service execute their compensating transactions.

Which approach to choose cannot be answered simply. Both have pros and cons and fit different scenarios. It depends on the size of your team and the overall scope. Make sure you understand the implications of each approach and have mitigations for failure. In the orchestration approach, you must manage another service and have a good backup plan for orchestrator failure. In the choreography approach, you must think about all possible events and, for example, what happens when an event is missed (either because of failure on the producer or the consumer side).