Что такое паттерн Saga и когда его использовать
В монолите ACID-транзакция гарантирует, что все изменения либо сохранятся, либо откатятся вместе. В микросервисах данные размазаны по разным БД, и нет механизма, который атомарн...
🟢 Junior Level
Saga — это паттерн для управления распределёнными транзакциями в микросервисах.
В монолите ACID-транзакция гарантирует, что все изменения либо сохранятся, либо откатятся вместе. В микросервисах данные размазаны по разным БД, и нет механизма, который атомарно изменит всё сразу.
В монолите есть одна база данных и обычные транзакции (ACID). В микросервисах у каждого сервиса своя БД, и нельзя сделать одну транзакцию на несколько сервисов.
Saga решает эту проблему:
- Разбивает большую транзакцию на последовательность локальных транзакций
- Каждая локальная транзакция — в своём сервисе
- Если что-то пошло не так — выполняются компенсирующие транзакции (откат)
Пример — заказ в интернет-магазине:
1. Order Service: создать заказ
2. Payment Service: списать деньги
3. Inventory Service: зарезервировать товар
4. Shipping Service: отправить
Если Payment упал → отменить заказ (компенсация)
Если Inventory упал → вернуть деньги + отменить заказ
// Что видит пользователь: заказ создан → оплата прошла → инвентарь зарезервирован.
// Если Shipping упал: compensation отменяет всё, пользователь видит "заказ отменён".
// Eventual consistency занимает обычно секунды-минуты.
🟡 Middle Level
Два типа Saga
1. Choreography (Хореография):
Каждый сервис публикует событие → другие сервисы слушают и реагируют
Order Service → "OrderCreated" → Payment Service
Payment Service → "PaymentCompleted" → Inventory Service
Inventory Service → "InventoryReserved" → Shipping Service
2. Orchestration (Оркестрация):
Центральный оркестратор управляет всем процессом
Orchestrator
/ | \ \
Order Payment Inventory Shipping
Когда использовать
✅ Saga подходит когда:
- Транзакция затрагивает несколько сервисов
- Нужна eventual consistency (не мгновенная согласованность)
- Нет единой БД
❌ Если бизнес-требования юридически требуют строгой ACID (банковские переводы), Saga может не подойти без дополнительных гарантий (блокировки, двухфазный коммит).
Типичные ошибки
- Забытая компенсация:
Order → Payment → Inventory (fail) Нужно: refund(payment) + cancel(order) Если забыли refund → деньги списаны, заказа нет
🔴 Senior Level
Internal Implementation
Saga log:
Каждая Saga должна логировать:
1. Начатые шаги
2. Завершённые шаги
3. Компенсации
Это позволяет восстановить состояние после рестарта
Архитектурные Trade-offs
| Хореография | Оркестрация |
|---|---|
| Нет единой точки отказа | Оркестратор — single point of failure |
| Сложно отследить поток | Легко мониторить |
| Циклические зависимости | Нет циклических зависимостей |
| Хорошо для простых Saga | Хорошо для сложных Saga |
Edge Cases
1. Idempotency:
Компенсирующая транзакация может вызваться дважды
→ Нужна идемпотентность
refund(paymentId):
if alreadyRefunded(paymentId): return
doRefund(paymentId)
markRefunded(paymentId)
2. Partial failure:
Saga: A → B → C (fail)
Компенсации: compensate(B) → compensate(A)
Если compensate(B) тоже упала → retry + alert
Production Experience
Order Saga:
@SagaDefinition
public class OrderSaga {
@StartSaga
@SagaEventHandler(associationProperty = "orderId")
public void handle(OrderCreatedEvent event) {
SagaLifecycle.start();
commandGateway.send(new ReserveInventoryCommand(event.orderId()));
}
@SagaEventHandler(associationProperty = "orderId")
public void handle(InventoryReservedEvent event) {
commandGateway.send(new ProcessPaymentCommand(event.orderId()));
}
@SagaEventHandler(associationProperty = "orderId")
public void handle(PaymentCompletedEvent event) {
commandGateway.send(new ShipOrderCommand(event.orderId()));
SagaLifecycle.end();
}
@SagaEventHandler(associationProperty = "orderId")
public void handle(InventoryReservationFailedEvent event) {
// Компенсация — отменить заказ
commandGateway.send(new CancelOrderCommand(event.orderId()));
SagaLifecycle.end();
}
}
Best Practices
✅ Логируйте каждый шаг Saga
✅ Реализуйте idempotency для компенсаций
✅ Используйте timeouts для каждого шага
✅ Мониторьте задержки между шагами
❌ Не используйте для простых CRUD операций
❌ Не забывайте про компенсирующие транзакции
❌ Не игнорируйте partial failures
🎯 Шпаргалка для интервью
Обязательно знать:
- Saga — паттерн для распределённых транзакций без единой БД
- Два типа: Choreography (event-driven) и Orchestration (coordinator-driven)
- Компенсирующие транзакции отменяют шаги в обратном порядке
- Eventual consistency — данные согласованы не мгновенно
- Idempotency критична для компенсаций
- Transactional Outbox гарантирует доставку событий
- Хореография — 2-3 сервиса, Оркестрация — 5+ сервисов
- Saga timeout защищает от зависших транзакций
Частые уточняющие вопросы:
- Чем Saga отличается от 2PC? Saga не блокирует ресурсы, работает с гетерогенными БД, но даёт eventual consistency вместо строгой ACID.
- Что будет если компенсация тоже упадёт? Retry + alert для manual intervention.
- Когда НЕ использовать Saga? Для простых CRUD-операций в одном сервисе или когда нужна строгая ACID (банковские переводы).
- Как мониторить Saga? Логируйте каждый шаг, отслеживайте длительность между шагами, используйте distributed tracing.
Красные флаги (НЕ говорить):
- “Saga гарантирует ACID” — нет, eventual consistency
- “Компенсация = ROLLBACK” — нет, это новая транзакция
- “Хореография лучше для 10 сервисов” — нет, оркестрация
- “Saga не нужна если есть Kafka” — Kafka лишь транспорт, логика всё равно нужна
Связанные темы:
- [[2. В чём разница между хореографией и оркестрацией в Saga]]
- [[3. Как реализовать распределённые транзакции в микросервисах]]
- [[4. Что такое компенсирующие транзакции]]
- [[13. Что такое паттерн Database per Service]]
- [[22. Что такое distributed tracing]]