2PC vs Saga на собеседовании системного аналитика

Готовься к собесу аналитика как в Duolingo
10 минут в день — SQL, Python, A/B, метрики. 1700+ вопросов в Telegram
Открыть Карьерник в Telegram

Карьерник — Duolingo для аналитиков: 10 минут в день тренируй SQL, Python, A/B, статистику, метрики и ещё 3 темы собеса. 1500+ вопросов в Telegram-боте. Бесплатно.

Зачем спрашивают на собесе SA

Микросервисы → распределённые транзакции — типичный сценарий собеса SA на middle/senior. «Заказ → платёж → склад → доставка — как обеспечить согласованность». Знание 2PC и saga — must-have для проектирования интеграций.

Главная боль без понимания — SA закладывает в ТЗ «всё в одной транзакции», получает ответ от тимлида «у нас 4 сервиса с разными БД», и переписывает с нуля.

Проблема распределённых транзакций

В монолите ACID БД даёт всё бесплатно: либо все операции применились, либо ни одна. В микросервисах:

  • Каждый сервис имеет свою БД.
  • Сервис не может «откатить» изменение в другом сервисе синхронно.
  • Сеть ненадёжна — запрос может зависнуть, упасть, дойти дважды.

Пример: пользователь оформляет заказ. Нужно:

  1. Создать заказ в Orders Service.
  2. Зарезервировать товар в Inventory Service.
  3. Списать деньги в Payment Service.
  4. Создать доставку в Shipping Service.

Если шаг 3 упал, что делать с уже резервированным товаром и созданным заказом? «Просто оставить в pending» — это нарушение пользовательского опыта.

Решения: 2PC, saga, eventual consistency.

Two-Phase Commit (2PC)

Координатор + N участников.

Фаза 1 — prepare:

Coordinator → Participant 1: PREPARE?
Coordinator → Participant 2: PREPARE?
Coordinator → Participant 3: PREPARE?

Каждый участник проверяет, может ли он закоммитить, и отвечает YES (готов) или NO (отказ). При YES — резервирует ресурс и держит его до решения координатора.

Фаза 2 — commit/abort:

Если все ответили YES → координатор шлёт COMMIT всем. Если хотя бы один NOABORT.

Coordinator → All: COMMIT

Минусы:

  • Блокировки на ресурсах. Между prepare и commit участник держит lock — другие транзакции ждут.
  • Координатор — single point of failure. Падение между фазами оставляет участников в подвешенном состоянии.
  • Не подходит для долгих операций. Минута блокировки — катастрофа для high-load системы.
  • Требует поддержки от всех участников. REST-сервисы обычно не имеют 2PC из коробки.

Где используется:

  • Внутри одной БД (распределённая БД, GP, шардированный Postgres через FDW).
  • Корпоративные системы с XA transactions (Java EE).
  • Очень редко в современных микросервисах на REST.

3PC (three-phase commit) — попытка убрать блокировки, добавлен pre-commit. На практике в продакшене почти не встречается.

Saga: компенсирующие транзакции

Идея. Длинная бизнес-транзакция разбивается на последовательность локальных транзакций. Каждая локальная либо завершается успешно, либо запускает компенсирующую транзакцию, чтобы откатить уже сделанное.

T1: создать заказ        → Compensation: отменить заказ
T2: зарезервировать товар → Compensation: вернуть товар
T3: списать деньги        → Compensation: вернуть деньги
T4: создать доставку      → Compensation: отменить доставку

Если T3 падает — выполняем компенсации T2 и T1. T4 не делаем.

Свойства:

  • Не атомарно. Между шагами система видит «частично выполненный» заказ. Это требует отдельных статусов: pending, reserved, paid, shipped.
  • Не изолировано. Параллельные транзакции видят промежуточные состояния. Лечение — semantic locks (статус pending блокирует другие действия с заказом).
  • Долговечно. Каждая локальная транзакция коммитит и не блокирует ресурсы.

Компенсация ≠ инверсия. Списание денег обратно — это не «отмена», а новая транзакция «возврат». В бухгалтерии всё прозрачно — оба события видны.

Choreography vs Orchestration

Choreography (хореография). Каждый сервис подписан на события и реагирует. Нет центрального координатора.

Orders → событие "OrderCreated"
Inventory подписан → резервирует → событие "ItemReserved"
Payment подписан → списывает → событие "PaymentProcessed"
Shipping подписан → создаёт доставку → событие "ShipmentCreated"

При ошибке — событие xxxFailed запускает компенсации.

Плюсы: слабая связанность, легко добавить новый сервис. Минусы: сложно отслеживать статус — приходится строить «event tracker», логика размазана по сервисам.

Orchestration (оркестрация). Центральный координатор (Saga Orchestrator) знает все шаги.

Orchestrator:
  Order.create()
  Inventory.reserve()
  Payment.charge()
  Shipping.create()

При ошибке — оркестратор знает, какие компенсации запустить.

Плюсы: видимая логика, легко мониторить и менять. Минусы: оркестратор сам становится критическим. Реализуется через workflow engines (Temporal, Camunda, AWS Step Functions).

Выбор:

  • 2-3 шага → choreography может быть проще.
  • 5+ шагов или сложная логика откатов → orchestration.
  • Регуляторика, аудит → orchestration (явный контроль).
Готовься к собесу аналитика как в Duolingo
10 минут в день — SQL, Python, A/B, метрики. 1700+ вопросов в Telegram
Открыть Карьерник в Telegram

Eventual consistency и outbox pattern

Eventual consistency — системы согласованны не сразу, а «в конечном счёте». Saga — частный случай.

Проблема: dual write. Сервис должен сохранить в свою БД и опубликовать событие в Kafka. Если сохранил, но Kafka недоступна — теряем событие.

Outbox pattern.

  1. В одной локальной транзакции сохранить и бизнес-данные, и запись в outbox таблицу.
  2. Отдельный воркер читает outbox, публикует в Kafka, помечает запись как отправленную.
  3. Если воркер падает — при перезапуске продолжает с непомеченных записей.

Гарантия: at-least-once delivery. Идемпотентность на стороне получателя обязательна.

Inbox pattern — симметричный приём: сохраняем входящее событие в inbox до обработки, чтобы при retry не обработать дважды.

Когда что выбирать

Внутри одной БД (даже распределённой): транзакции БД, иногда XA / 2PC. Не пытайся изобретать велосипед.

2-3 микросервиса с быстрой работой: saga через choreography (Kafka events).

5+ сервисов, сложная логика отката, регуляторика: orchestration через Temporal / Camunda.

Нужна строгая консистентность сразу: пересмотри границы сервисов. Возможно, два сервиса должны быть одним.

Нужна гарантия доставки события: outbox pattern + at-least-once consumer + идемпотентность.

Частые ошибки

Использовать 2PC между микросервисами на REST. Технически возможно (XA proxy), на практике — пытка. Saga — стандарт.

Считать saga «слабее» транзакции БД. Это другой класс согласованности (eventual). Не «хуже», а «иначе». Подходит для бизнес-сценариев.

Не реализовать компенсации. Saga без компенсаций — просто цепочка вызовов. При первой ошибке система зависает.

Idempotency опционально. В eventual consistency сообщения дублируются. Каждый шаг должен быть идемпотентен (по idempotency_key или natural key).

Игнорировать «orphaned» состояния. При компенсации может оказаться, что часть данных уже отправлена клиенту (письмо, push). Компенсация должна учитывать «снаружи видимые» эффекты.

Не использовать correlation_id. Без него отследить saga в логах невозможно. Каждое событие должно нести saga_id / correlation_id.

Делать 20-шаговую saga. Тяжело отлаживать, тяжело поддерживать. Дробить на под-saga.

Путать orchestration с monolith logic. Оркестратор — координатор, а не реализация бизнес-логики. Логика остаётся в сервисах.

Связанные темы

FAQ

Saga гарантирует консистентность?

Eventual consistency. В каждый момент времени БД сервисов могут быть несогласованны — но «в конечном счёте» либо все шаги выполнены, либо все компенсированы.

Можно ли использовать 2PC в микросервисах?

Технически — да (XA, REST + custom protocol). На практике — никто не делает: блокировки и сетевая нестабильность убивают идею.

Почему Kafka не решает всё?

Kafka — транспорт. Гарантирует at-least-once или exactly-once на топик. Но бизнес-логика «оформить заказ через 4 сервиса» требует saga поверх Kafka — Kafka сама не знает про откат.

Что такое Process Manager?

Альтернативное название Saga Orchestrator. Один из паттернов из «Enterprise Integration Patterns» (Hohpe).

Как тестировать saga?

End-to-end — happy path и каждый сценарий компенсации. Chaos-инжекция (убить сервис в середине saga) — обязательно для критических.

Это официальная информация?

Нет. Статья основана на работах Microservices.io (Chris Richardson), Hohpe «EIP», документации Temporal/Camunda.


Тренируйте системный анализ — откройте тренажёр с 1500+ вопросами для собесов.