Саги и компенсирующие транзакции: что ломается внутри
Заказ, оплата и резерв товара живут в разных сервисах, поэтому одной транзакцией их не защитить. Разбираем, как сага выполняет цепочку действий, где появляются ошибки и что проверять под нагрузкой.

Сервис заказа может успешно создать заказ, платежный сервис — списать деньги, а сервис склада — не зарезервировать товар. При классической транзакции базы данных эти изменения откатились бы вместе, но в микросервисной системе общего атомарного commit обычно нет. Сага заменяет его последовательностью локальных транзакций и компенсирующих действий. Цена решения — временно несогласованное состояние, сложная обработка повторов и необходимость отдельно проектировать откат.
Что именно делает сага
Сага разбивает бизнес-операцию на несколько шагов. Каждый шаг фиксируется локальной транзакцией в своем сервисе. После успешного шага запускается следующий, а при сбое система выполняет заранее определенное компенсирующее действие для уже завершенных шагов.
Представим оформление заказа:
- Order Service создает заказ со статусом
NEW. - Payment Service авторизует или списывает платеж.
- Inventory Service резервирует товар.
- Order Service переводит заказ в
CONFIRMED.
Если резерв не удался после успешной оплаты, компенсацией может стать отмена авторизации или возврат средств. Это не «откат» в смысле базы данных: операция выполняется новой командой, которая сама может задержаться, завершиться с ошибкой или потребовать повтора.
Компенсация не возвращает систему в прошлое. Она переводит бизнес-объект в новое допустимое состояние после частично выполненной операции.
Сага — распределенная последовательность локальных транзакций, согласованность которой достигается событиями или командами, а не общей транзакцией между базами.
Два способа связать шаги
У саги есть два распространенных варианта координации. Они решают одну задачу, но по-разному распределяют ответственность за порядок операций и обработку ошибок.
Хореография: сервисы реагируют на события
В хореографии отдельного координатора нет. Order Service публикует событие OrderCreated, платежный сервис реагирует на него и публикует PaymentAuthorized, после чего сервис склада запускает резервирование.
Плюс — меньше центральной логики и слабее связность с одним оркестратором. Минус появляется, когда шагов становится больше: порядок событий приходится восстанавливать по подписчикам, а путь компенсации — искать по нескольким кодовым базам. Ошибка в маршрутизации может остаться незаметной, если у события нет надежного мониторинга.
Оркестрация: отдельный координатор управляет шагами
Оркестратор хранит состояние саги и отправляет команды: авторизовать платеж, зарезервировать товар, подтвердить заказ. Он получает результаты, выбирает следующий шаг и знает, какие компенсации запускать при ошибке.
Так проще увидеть полный процесс и строить контрольные таймеры. Но оркестратор становится важным компонентом: его отказ, неверная версия схемы состояния или потеря команды могут остановить большое число бизнес-операций. Оркестратор не должен превращаться в место, где собрана вся бизнес-логика сервисов.
| Критерий | Хореография | Оркестрация |
|---|---|---|
| Где находится порядок шагов | Распределен между обработчиками событий | В состоянии и правилах оркестратора |
| Наблюдаемость маршрута | Сложнее: нужно связывать события разных сервисов | Проще: один компонент видит состояние саги |
| Добавление шага | Меньше изменений в центральном коде, но больше скрытых связей | Изменяется сценарий оркестратора и контракт нового шага |
| Основной риск | Циклические зависимости и неявный порядок | Центральная точка отказа и перегрузка оркестратора |
| Когда подходит | Короткая цепочка с хорошо выделенными событиями | Длинный процесс с таймерами, ветвлениями и компенсациями |
Как проходит сбой внутри
Ключевой объект саги — не только команда, но и ее состояние. Для каждой операции нужно хранить идентификатор саги, идентификатор шага, текущий статус и число попыток. Без этого повторная доставка сообщения может снова списать деньги или создать второй резерв.
1. Локальная транзакция и исходящее сообщение
Сервис должен зафиксировать изменение своей базы и намерение отправить событие согласованно. Для этого часто используют transactional outbox: в одной локальной транзакции записывают бизнес-изменение и сообщение в таблицу исходящих сообщений. Отдельный отправитель позже публикует сообщение в брокер.
Outbox не делает всю сагу атомарной. Он лишь снижает риск ситуации, когда запись уже сохранена, а событие не ушло из-за падения процесса.
2. Повторная доставка и идемпотентность
Брокер или отправитель могут повторить сообщение после тайм-аута. Получатель должен распознать уже обработанный шаг по ключу идемпотентности и вернуть сохраненный результат, а не выполнить побочный эффект заново.
Для платежа таким эффектом может быть повторное списание, для склада — увеличение резерва, для уведомлений — дубликат письма. Простая проверка «заказ уже существует» не всегда достаточна: нужно учитывать тип операции, версию команды и ее результат.
3. Тайм-аут и неопределенный результат
Тайм-аут означает, что вызывающая сторона не получила ответ, но не доказывает, что операция не произошла. Сервис оплаты мог списать деньги и упасть до отправки ответа. Поэтому после тайм-аута нельзя без проверки считать платеж неуспешным и безопасно повторять списание.
Рабочая схема использует запрос статуса, идемпотентный ключ и отдельное состояние вроде PAYMENT_UNKNOWN. Финальное решение принимается после сверки с платежным сервисом или его журналом операций.
4. Компенсация
Компенсирующая команда должна быть отдельной бизнес-операцией. Для «резерв создан» это может быть «снять резерв», для «платеж авторизован» — «отменить авторизацию», для «доставка заказана» — «отменить заявку на доставку».
Компенсация может быть несимметричной. Если письмо уже отправлено, его нельзя надежно «отправить назад». В таком случае система создает корректирующее уведомление, а не пытается имитировать невозможный откат.
Что измерять, а не угадывать
У саг нет универсального безопасного значения тайм-аута или числа повторов. Их подбирают по реальным задержкам конкретных сервисов и проверяют на нагрузочном стенде. В отчете полезно разделять время бизнес-операции и время доставки сообщений.
- доля саг, завершившихся успешно с первой попытки;
- время от запуска до финального состояния, включая компенсацию;
- число повторных доставок и повторных попыток по каждому шагу;
- возраст незавершенных саг;
- количество операций в состояниях
UNKNOWN,COMPENSATINGиFAILED; - время нахождения сообщений в очереди и число сообщений в dead-letter очереди.
Замер нужно проводить с зафиксированными объемом данных, числом параллельных операций, конфигурацией брокера и лимитами баз данных. Сравнение «до и после» без этих условий показывает только изменение стенда, а не пользу оптимизации.
Граничные случаи и места поломки
Компенсация тоже падает
Сервис возврата может быть недоступен, а платежная система — отвечать медленно. Поэтому компенсации нужны собственные повторы, дедлайны, журнал попыток и маршрут для ручной обработки. Бесконечный автоматический retry опасен: он создает нагрузку и может повторять уже выполненную операцию.
События приходят не по порядку
Событие об отмене может прийти раньше события о создании резерва. Обработчик должен либо хранить отложенное событие, либо уметь запросить актуальное состояние операции. Надежда на порядок доставки между разными типами сообщений не заменяет проверку версии состояния.
Пользователь требует результат сразу
Сага часто приводит к модели eventual consistency[1]: некоторое время интерфейс показывает промежуточный статус. Если бизнес не принимает такую задержку, нужно явно выбрать другой механизм: локальную транзакцию в одном сервисе, синхронный вызов с понятным статусом или пересмотр границ владения данными.
Компенсация нарушает новое состояние
За время между списанием и возвратом пользователь мог изменить адрес, товар мог закончиться, а заказ — перейти в спор. Компенсация должна проверять актуальные бизнес-условия, а не механически повторять обратную команду. Особенно опасны операции, которые нельзя отменить: отправка товара, выдача цифрового ключа или передача данных внешнему партнеру.
[1] Eventual consistency, или согласованность в конечном счете, означает, что реплики и сервисы могут временно показывать разные состояния, но при успешной обработке событий должны прийти к согласованному результату.
Что ревьюить в реализации
Ревью саги стоит начинать не с названий классов, а с таблицы переходов состояний. Для каждого шага должны быть понятны команда, успешный результат, ошибка, повтор, компенсация и финальный статус.
- Есть ли уникальный идентификатор саги и операции, который проходит через логи всех сервисов.
- Можно ли повторить каждую команду без второго побочного эффекта.
- Что происходит после тайм-аута, когда результат операции неизвестен.
- Записываются ли изменение данных и исходящее событие в одной локальной транзакции.
- Есть ли защита от устаревшего события и повторной компенсации.
- Кто разберет зависшую сагу и как оператор увидит причину, не читая логи пяти сервисов.
Фраза «при ошибке делаем rollback» в ревью недостаточна. Нужно назвать конкретную компенсирующую операцию и описать, что произойдет, если она тоже завершится с ошибкой.
Что делать дальше
Начните с одной бизнес-цепочки и нарисуйте ее как конечный автомат: состояния, команды, события, тайм-ауты и компенсации. Затем добавьте корреляционный идентификатор, журнал переходов и метрику возраста незавершенных саг. После этого проверьте сценарии падения между каждой парой действий: до записи, после записи, до отправки сообщения и после отправки без ответа. Для конкретной реализации сверяйте поведение брокера, драйвера базы и библиотек с документацией их версий; сам паттерн саги не привязан к одной версии фреймворка.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: Saga pattern — Microservices.io, Saga orchestration pattern — Azure Architecture Center
Частые вопросы
Сага гарантирует, что заказ в итоге всегда будет согласован?
Чем сага отличается от распределенной транзакции?
Что делать, если сообщение доставили два раза?
Что делать, если сервис не ответил после команды?
Нужна ли сага для двух сервисов и одного простого шага?
Что изменится, если у меня другая версия брокера или фреймворка?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.