Guide

A single business action often touches two systems. Inventory and payment must agree. A message and its push notification must agree. A ledger and an external payment provider must agree. You cannot wrap both in one transaction, so you need a pattern that survives a crash between the steps.

Concept

Before you start. A saga is a sequence of local steps. Each step has a compensating step that undoes it, if a later step fails. An outbox is a table where an event is written in the same transaction as the data it describes, then relayed to a queue. An idempotency key is a stable ID that makes a retried request a no-op. Reconciliation is a job that compares two systems and fixes the drift.

The big idea

Think of booking a trip. You book a flight, then a hotel. If the hotel fails, you cancel the flight. Each booking is a step, and each cancellation is a compensation.

The analogy hides a limit. Some undo steps are not perfect. A refund is not the same as a payment that never happened, and an email that was already sent cannot be unsent.

Walking through it

A ticket purchase runs in four steps. The third one fails.

1. Hold seat 14C                 ok
2. Authorize the card            ok
3. Commit seat 14C               fails, sold out elsewhere
4. Void the authorization        compensating step, the card is never charged

The outbox fixes a common bug. A service writes an order to its database, then publishes an event. If it crashes between the two, the order exists but nobody hears about it.

One transaction:
  INSERT order row
  INSERT outbox row ("OrderPlaced", order 881)
COMMIT

Relay (separate process):
  read unsent outbox rows, publish to the queue, mark sent

Each hop also takes an idempotency key. A retried payment with the same key returns the first result, and does not charge twice. Finally, a nightly reconciliation job compares orders with payments, and flags any mismatch for a person or an automatic fix.

Common mistakes

  • Publishing an event after the database write, not inside the same transaction. A crash between the two loses the event.
  • Treating a compensation as a perfect undo. A refund leaves a trace. Design the business rule for that.
  • Retrying without an idempotency key. A retry can charge twice.
  • Skipping reconciliation. Drift between two systems grows silently until someone notices.

Check yourself

  1. Why use an outbox instead of publishing right after the database write? A crash between the write and the publish loses the event. Writing the event in the same transaction removes that gap.
  2. What does a compensating step do in a saga? It undoes an earlier completed step, such as voiding an authorization, instead of rolling back one transaction.

Key takeaways

  • A saga runs local steps, each with a compensation for failure.
  • An outbox writes events in the same transaction as the data they describe.
  • Idempotency keys make every retry safe.
  • Reconciliation catches the drift that all of the above can miss.

Further learning

For the trade-offs of full distributed transactions, see distributed transactions.

For the repeat-safe design behind the keys, see idempotency.

Next, see how long-running work and live updates are delivered, in realtime and long-running work.