CodePark/ questions / Event-driven design, sagas & outbox
START PRACTICINGPRACTICE
Backend question · System design & architecture › Event-driven design, sagas & outbox

Placing an order touches the Order, Payment and Inventory services, each with its own database. How does the saga pattern keep data consistent, and why not use a distributed transaction (2PC)?

A saga keeps data consistent with a sequence of local transactions, one per service. Each step commits in its own database. Then it triggers the next step with an event or a command.

If a step fails, the saga runs compensating transactions. These undo the earlier steps in business terms. For an order, they might:

  • release the reserved stock
  • refund the payment
  • mark the order as cancelled

Compensations are business actions, not database rollbacks. So you must design one for each step. Steps should also be idempotent: running a step twice has the same effect as running it once. This matters because the broker can deliver a message more than once.

Sagas also lack isolation. Other requests can see in-between states such as "payment pending". So you design for that with explicit states.

2PC (two-phase commit) is avoided for these reasons:

  • It holds locks across services while they wait for a coordinator.
  • It blocks if the coordinator fails.
  • It ties the availability of all the services together.
  • Many message brokers and NoSQL stores do not support it.
  • A saga is a sequence of local transactions, one per service.
  • Compensating transactions undo the earlier steps.
  • Steps must be idempotent, and in-between states are visible.
  • Sagas have no isolation, so you design explicit states.
  • 2PC blocks, holds locks and ties services' availability together.