SAGA Orchestration for Distributed Transactions

March 10, 2026

SAGA Orchestration for Distributed Transactions

A single beneficiary payout in RAHAT touches a handful of services in sequence. Identity validation, bank verification, a blockchain settlement call, a notification step. None of these can live inside one database transaction because they span different systems, different latencies, and sometimes different networks entirely. That's more or less the exact problem SAGA orchestration was built to solve.

Why not just use a distributed transaction?

Two phase commit sounds fine until you remember it needs every participant online and responsive at the same moment. In humanitarian fieldwork that assumption breaks constantly. A bank verification API can be slow, a field device can drop offline, a blockchain confirmation can take a couple of extra blocks. Holding locks across all of that just isn't realistic.

A saga breaks the payout into a sequence of small local transactions instead, and each one has a compensating action if something downstream fails. Validate the beneficiary, and if the bank step fails later, release the hold. Reserve the funds, and if settlement fails, reverse the reservation. Nothing sits there locked waiting on a remote party. Each step commits on its own, and the saga either keeps moving forward or unwinds cleanly.

Orchestration over choreography

There are two common ways to coordinate a saga. Choreography has each service react to events from the one before it. Orchestration uses a central coordinator that explicitly tells each step what happens next and tracks the saga's state directly.

For the Group Cash Transfer module I went with orchestration. Choreography looks elegant on a whiteboard, but debugging "why didn't step four run" across five services each listening for someone else's event gets painful fast, especially when beneficiaries and auditors need a clear answer about the state of one specific transfer. An orchestrator gives you a single place to look, one state machine per saga instance, and a much simpler retry and compensate model.

Where Redis comes in

The orchestrator needs to serialize steps for a given beneficiary or transfer batch, since two payout attempts racing each other for the same person is exactly what you don't want. I used Redis backed queues to serialize per entity work. Each saga instance claims a lock keyed by beneficiary ID before it advances, and the next step for that entity just waits its turn instead of firing concurrently. Redis also holds the in flight saga state, so a crashed worker can pick a saga back up from wherever it left off instead of starting over.

What this bought us

No half completed payouts. A failure at any step reliably unwinds instead of leaving funds reserved with nowhere to go. Retries are idempotent, so a slow bank API or a dropped connection resolves itself without anyone having to step in manually. And every saga transition ends up being a discrete, loggable event, which matters a lot when the people relying on the system are humanitarian auditors tracing exactly what happened to a specific transfer.

None of this is exotic. It's just the right tool when correctness matters more than raw throughput, and for cash reaching people who actually need it, correctness always wins.

GitHub
LinkedIn