Вопрос 1 · Раздел 17

Что такое паттерн Saga и когда его использовать

В монолите ACID-транзакция гарантирует, что все изменения либо сохранятся, либо откатятся вместе. В микросервисах данные размазаны по разным БД, и нет механизма, который атомарн...

Версии по языкам: English Russian Ukrainian

🟢 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 может не подойти без дополнительных гарантий (блокировки, двухфазный коммит).

Типичные ошибки

  1. Забытая компенсация:
    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]]