Logo
Overview

Saga: распределённые транзакции в микросервисах — choreography vs orchestration

June 22, 2026
7 min read

Saga: распределённые транзакции в микросервисах — choreography vs orchestration

Saga — это не магия. И не очередной фреймворк, который «сам всё сделает». Это стратегия: как провести бизнес-операцию через несколько микросервисов и не оставить данные в противоречивом состоянии, когда (не «если») что-то пойдёт не так.

В монолите всё просто: ACID-транзакция внутри одной базы данных — и если на третьем шаге из пяти случилась ошибка, ROLLBACK откатывает всё назад. Красиво. В микросервисах каждая база данных живёт своей жизнью. Заказ создан в Order DB, деньги списаны в Payment DB, а на складе — отказ. Поздравляю: у вас заказ без товара, деньги у клиента, и тикет с тегом «critical» в Jira.

Вот тут на сцену и выходит Saga.

Зачем нужна Saga: проблема распределённых транзакций

Классическая ACID-транзакция опирается на один экземпляр базы данных и один координатор транзакций. Всё или ничего. В микросервисной архитектуре такой роскоши нет: каждый сервис владеет своими данными и своей базой.

Распределённая транзакция — бизнес-операция, которая изменяет данные в нескольких независимых сервисах и должна быть выполнена атомарно: либо все изменения фиксируются, либо ни одно.

Первое, что приходит в голову — двухфазный коммит (2PC). Да, он существует. Да, он даже работает. Но 2PC блокирует ресурсы на время всей транзакции: если Payment Service отвечает 15 секунд, Order Service висит с открытой транзакцией и блокировками. В распределённой системе с десятками сервисов и тысячами запросов в секунду это не масштабируется. Совсем.

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

Два подхода: Choreography vs Orchestration

Паттерн Saga реализуется двумя принципиально разными способами. Оба решают одну задачу, но устроены по-разному, как ручная коробка передач и автомат.

Choreography (хореография)

При хореографии нет центрального координатора. Сервисы обмениваются событиями напрямую: один публикует событие — другие, кто на него подписан, реагируют.

100%
flowchart LR
  subgraph Choreography["Хореография: события без координатора"]
      OS["Order Service
Создаёт заказ
PENDING"] -->|"Event
OrderCreated"| PS
      PS["Payment Service
Списывает средства"] -->|"Event
PaymentCompleted"| IS
      IS["Inventory Service
Резервирует товары"] -->|"Event
ItemsReserved"| SS
      SS["Shipping Service
Планирует доставку"] -->|"Event
ShipmentScheduled"| OS2["Order Service
Заказ
CONFIRMED"]
  end
  style Choreography fill:none,stroke:#4a90d9,stroke-width:2px
  style OS fill:#4a90d9,stroke:#2c5f8a,color:#fff
  style PS fill:#7b68ee,stroke:#5a4db2,color:#fff
  style IS fill:#f0a500,stroke:#c88400,color:#fff
  style SS fill:#50c878,stroke:#3a9a5c,color:#fff
  style OS2 fill:#4a90d9,stroke:#2c5f8a,color:#fff

На диаграмме видно: каждый сервис знает только о своём соседе по цепочке. Order Service публикует OrderCreated, Payment Service на него реагирует, публикует PaymentCompleted — и так до финального статуса. Никто не управляет процессом сверху.

Плюсы — низкая связанность и простота добавления новых сервисов. Захотелось отправлять push-уведомление после оплаты? Подписались на PaymentCompleted — готово, никто никого не трогал. Минусы — понять, на каком шаге застрял заказ, можно только проследив цепочку событий (и молиться, что они не потерялись по дороге).

Orchestration (оркестрация)

При оркестрации появляется Saga Orchestrator — отдельный компонент, который явно управляет последовательностью шагов. Он говорит каждому сервису: «создай заказ», «спиши деньги», «забронируй товар». И он же принимает решение об откате.

100%
sequenceDiagram
  participant C as Client
  participant SO as Orchestrator
  participant OS as Order Service
  participant PS as Payment Service
  participant IS as Inventory Service
  C->>SO: POST /create-order
  rect rgb(200, 230, 200)
      Note over SO,OS: Шаг 1: Создать заказ
      SO->>+OS: CreateOrder()
      OS-->>-SO: orderId=123
  end
  rect rgb(200, 220, 240)
      Note over SO,PS: Шаг 2: Списать средства
      SO->>+PS: ProcessPayment(orderId, 5000)
      PS-->>-SO: paymentId=456
  end
  rect rgb(250, 210, 210)
      Note over SO,IS: Шаг 3: Резервировать товары (FAIL)
      SO->>+IS: ReserveItems(orderId, items)
      IS-->>-SO: FAIL: Out of stock
  end
  rect rgb(255, 240, 200)
      Note over SO,PS: Компенсация 1: откат платежа
      SO->>+PS: RefundPayment(paymentId)
      PS-->>-SO: refunded
  end
  rect rgb(255, 240, 200)
      Note over SO,OS: Компенсация 2: отмена заказа
      SO->>+OS: CancelOrder(orderId)
      OS-->>-SO: cancelled
  end
  SO-->>C: 422 Order Failed

Цвета на диаграмме отражают фазы: зелёный — успешное выполнение шага, красный — сбой, жёлтый — компенсирующие действия. Orchestrator — мозг операции. Он хранит состояние саги (какие шаги выполнены, что пошло не так) и принимает решения.

Оркестрация заметно упрощает мониторинг: состояние саги лежит в одном месте, вам не нужно восстанавливать его, обходя события. Но Orchestrator становится единой точкой отказа (и умным местом, которое все забывают покрыть тестами).

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

В классическом ACID откат — это ROLLBACK. В Saga откат — это бизнес-логика. Если Payment Service списал деньги, а склад ответил «нет товара», нельзя просто сделать ROLLBACK в Payment DB: Payment Service — отдельный сервис, у него своя база (и он уже успел отправить клиенту «спасибо за покупку»).

Вместо этого выполняется компенсирующая транзакция — бизнес-операция, которая семантически отменяет предыдущий шаг. Списание денег компенсируется возвратом (RefundPayment). Создание заказа — отменой (CancelOrder). Важный нюанс: компенсация не обязана возвращать систему в точно то же состояние, что было до саги. В audit-логе останется запись «платёж создан», и это правильно.

Проектировать компенсации нужно сразу, на этапе описания каждого шага саги. Таблица ниже — минимальный шаблон:

ШагСервисДействиеКомпенсация если FAIL
1Order ServiceCreateOrderCancelOrder
2Payment ServiceProcessPaymentRefundPayment
3Inventory ServiceReserveItemsReleaseItems
4Shipping ServiceScheduleShipmentCancelShipment

Outbox Pattern: надёжная доставка событий

Проблема, о которой часто забывают: Saga опирается на сообщения и события. Если сервис обновил свою базу данных, но не смог отправить событие в брокер — сага «зависает». Order Service создал заказ и молчит. Payment Service ждёт. Клиент ждёт. Все ждут.

Outbox Pattern решает это атомарной записью: в той же транзакции, где сервис меняет свои данные, он пишет исходящее событие в таблицу outbox. Отдельный процесс (Change Data Capture или фоновый воркер) читает эту таблицу и публикует события в брокер сообщений.

Так вы гарантируете: либо и изменение данных, и событие записаны, либо ни того ни другого. Атомарность на уровне одного сервиса, которую нельзя обеспечить на уровне всей системы.

Практический пример: заказ в e-commerce

Представьте интернет-магазин. Пользователь кладёт товар в корзину и нажимает «Оплатить». За кулисами запускается сага из четырёх сервисов:

  1. Order Service создаёт заказ со статусом PENDING.
  2. Payment Service резервирует (холдирует) 5000 рублей на карте.
  3. Inventory Service резервирует товар на складе — и тут OutOfStockException.
  4. Saga Orchestrator запускает откат: возврат холда в Payment Service, отмена заказа в Order Service.

Что важно: статус заказа прошёл путь PENDINGPAYMENT_RESERVEDCANCELLED. Пользователь увидел «заказ отменён, средства вернутся в течение 3 дней». В идеальном мире всё отработало атомарно — никаких потерянных денег, никаких висящих резервов на складе. В реальном — ещё и написали логи на каждом шаге, чтобы понять, почему склад ответил отказом.

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

Choreography vs Orchestration: таблица сравнения

КритерийChoreographyOrchestration
СвязанностьСлабая (сервисы знают только события)Средняя (сервисы знают Orchestrator)
МониторингСложный (нужно восстановить цепочку событий)Простой (состояние в Orchestrator)
Добавление шаговПодписка на событие — без изменения кода других сервисовНужно менять Orchestrator
Циклические зависимостиВозможны (A → B → A)Контролируются централизованно
Единая точка отказаНет (каждый сервис независим)Да (Orchestrator)
ОтладкаРаспределённый trace по логамЛинейный trace в одном месте
Сложность компенсацийКаждый сервис сам обрабатывает ошибкиЦентрализованная логика в Orchestrator

Оба подхода имеют право на жизнь. Хореография хороша для простых цепочек (3–4 сервиса) и когда команды хотят оставаться максимально независимыми. Оркестрация — для процессов с ветвлениями, условной логикой и сложными компенсациями.

Когда Saga — не ваш выбор

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

  • Нет реальной потребности в микросервисах. Если у вас модульный монолит с одной базой данных, используйте обычные ACID-транзакции. Не усложняйте.
  • Бизнес-процесс короткий. Если операция меняет данные в двух сервисах и оба синхронны, Saga с её асинхронной моделью добавит latency без пользы.
  • Нет готовности к eventual consistency. После шага 2 заказ может висеть в статусе PAYMENT_RESERVED несколько секунд (и даже минут). Клиент увидит промежуточное состояние. Бизнес должен быть к этому готов.

Заключение

Saga — это не технология, а архитектурное решение. Выбор между хореографией и оркестрацией — это выбор между децентрализованной автономией команд и централизованным контролем бизнес-процесса. Первый вариант хорош, когда сервисов много и они меняются часто. Второй — когда бизнес-логика сложна, а её прозрачность важнее гибкости.

Главное, чему учит Saga: в распределённых системах ошибки — не исключение, а часть нормального режима работы. Проектируя компенсации на этапе архитектуры, вы перестаёте бояться словосочетания «отказ на третьем шаге». Вы к нему готовы.

Никакой магии. Просто дисциплина.