2PC vs Saga на собеседовании системного аналитика
Карьерник — Duolingo для аналитиков: 10 минут в день тренируй SQL, Python, A/B, статистику, метрики и ещё 3 темы собеса. 1500+ вопросов в Telegram-боте. Бесплатно.
Содержание:
Зачем спрашивают на собесе SA
Микросервисы → распределённые транзакции — типичный сценарий собеса SA на middle/senior. «Заказ → платёж → склад → доставка — как обеспечить согласованность». Знание 2PC и saga — must-have для проектирования интеграций.
Главная боль без понимания — SA закладывает в ТЗ «всё в одной транзакции», получает ответ от тимлида «у нас 4 сервиса с разными БД», и переписывает с нуля.
Проблема распределённых транзакций
В монолите ACID БД даёт всё бесплатно: либо все операции применились, либо ни одна. В микросервисах:
- Каждый сервис имеет свою БД.
- Сервис не может «откатить» изменение в другом сервисе синхронно.
- Сеть ненадёжна — запрос может зависнуть, упасть, дойти дважды.
Пример: пользователь оформляет заказ. Нужно:
- Создать заказ в
Orders Service. - Зарезервировать товар в
Inventory Service. - Списать деньги в
Payment Service. - Создать доставку в
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 всем. Если хотя бы один NO → ABORT.
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 (явный контроль).
Eventual consistency и outbox pattern
Eventual consistency — системы согласованны не сразу, а «в конечном счёте». Saga — частный случай.
Проблема: dual write. Сервис должен сохранить в свою БД и опубликовать событие в Kafka. Если сохранил, но Kafka недоступна — теряем событие.
Outbox pattern.
- В одной локальной транзакции сохранить и бизнес-данные, и запись в
outboxтаблицу. - Отдельный воркер читает
outbox, публикует в Kafka, помечает запись как отправленную. - Если воркер падает — при перезапуске продолжает с непомеченных записей.
Гарантия: 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. Оркестратор — координатор, а не реализация бизнес-логики. Логика остаётся в сервисах.
Связанные темы
- CAP теорема на собесе SA
- ACID и уровни изоляции на собесе SA
- Идемпотентность API для SA
- Монолит vs микросервисы на собесе SA
- Подготовка к собесу системного аналитика
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+ вопросами для собесов.