Saga Pattern — Distributed Transactions Without 2PC
Long-running distributed transactions via a sequence of local transactions, each with a compensating rollback action.
When to use
- Multi-service transactions (order → payment → inventory)
- 2PC is not available or too risky for availability
- Eventual consistency is acceptable
Tradeoffs
- Compensating transactions add complexity (what if compensation fails?)
- Only eventual consistency — no atomic guarantee
- Go
- Python
type Step struct {
Execute func() error
Compensate func() error
}
func runSaga(steps []Step) error {
done := []Step{}
for _, s := range steps {
if err := s.Execute(); err != nil {
for i := len(done) - 1; i >= 0; i-- {
done[i].Compensate()
}
return err
}
done = append(done, s)
}
return nil
}
from typing import Callable
class Step:
def __init__(self, execute: Callable, compensate: Callable):
self.execute = execute
self.compensate = compensate
def run_saga(steps: list[Step]) -> None:
done: list[Step] = []
for step in steps:
try:
step.execute()
done.append(step)
except Exception:
for s in reversed(done):
s.compensate()
raise
Gotcha: Choreography is simpler to start but hard to trace. Orchestration is verbose but every step is visible in one place.