Skip to main content

Distributed Transactions — 2PC vs Saga

Coordinating writes across multiple services so they all succeed or all roll back — 2PC for strong consistency, Saga for availability.

When to use

  • 2PC: small-scale, homogeneous DB cluster requiring strong consistency
  • Saga: microservices with high availability requirements, eventual consistency acceptable

Tradeoffs

  • 2PC blocks on coordinator failure (availability risk)
  • Saga needs compensating transactions (complexity, no atomicity guarantee)
type SagaStep struct {
Execute func(ctx context.Context) error
Compensate func(ctx context.Context) error
}

func RunSaga(ctx context.Context, steps []SagaStep) error {
executed := make([]SagaStep, 0, len(steps))
for _, step := range steps {
if err := step.Execute(ctx); err != nil {
// compensate in reverse order
for i := len(executed) - 1; i >= 0; i-- {
_ = executed[i].Compensate(ctx)
}
return err
}
executed = append(executed, step)
}
return nil
}

Gotcha: 2PC at microservice scale creates a blocking protocol that destroys availability. Use Saga instead — and test your compensations.