Перейти к основному содержимому

6.11. Saga

Разработчику Архитектору Аналитику

Saga

Паттерн Saga для управления распределёнными транзакциями

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

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

Когда бизнес-процесс затрагивает несколько микросервисов, например, создание заказа, резервирование товара на складе и списание средств с банковского счёта клиента, возникает потребность гарантировать согласованность данных во всех этих системах. Если один из шагов завершится ошибкой, все предыдущие успешные действия должны быть отменены, чтобы система осталась в целостном состоянии. В мире реляционных баз данных эту задачу решала двухфазная фиксация (2PC), но она плохо масштабируется, создаёт блокировки и снижает доступность системы. Микросервисная архитектура стремится к высокой доступности и независимости компонентов, поэтому требует иного подхода к управлению транзакциями.

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

Определение паттерна Saga

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

Важно понимать, что Saga не обеспечивает строгой изоляции, как это делает классическая ACID-транзакция. В промежутке между выполнением шагов другие процессы могут видеть промежуточное состояние системы. Это свойство называется eventual consistency — конечная согласованность. Система гарантирует, что после завершения всей Saga (успешного или с компенсацией) данные будут согласованы, но в процессе выполнения возможны временные несогласованные состояния.

Два основных стиля реализации Saga

Существует два основных подхода к реализации паттерна Saga: хореография (choreography) и оркестрация (orchestration). Оба подхода решают одну и ту же задачу, но делают это разными способами, с разными компромиссами в плане связанности, наблюдаемости и сложности.

Хореография

В этом стиле каждый микросервис сам принимает решение о том, как реагировать на события, публикуемые другими сервисами. Нет центрального координатора. Процесс выглядит как цепочка реакций: сервис A завершает свою локальную транзакцию и публикует событие «Заказ создан». Сервис B, подписанный на это событие, получает его, выполняет резервирование товара и публикует событие «Товар зарезервирован». Сервис C, в свою очередь, реагирует на это событие, списывает деньги и публикует «Платёж обработан».

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

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

Оркестрация

В этом стиле существует специальный сервис — оркестратор (saga orchestrator), который полностью управляет потоком выполнения Saga. Он знает все шаги процесса, их порядок и условия перехода. Оркестратор последовательно вызывает соответствующие микросервисы, дожидается их ответа и принимает решение о дальнейших действиях. Если вызов успешен, он переходит к следующему шагу. Если произошла ошибка, он запускает компенсирующие вызовы в обратном порядке.

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

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

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

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

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

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

Управление состоянием и идемпотентность

Поскольку Saga может выполняться в течение длительного времени (минуты, часы, даже дни), она должна надёжно сохранять своё текущее состояние. Это позволяет восстановить процесс после сбоя оркестратора или любого из сервисов. Состояние Saga обычно хранится в отдельной базе данных или в виде записей в очереди сообщений.

Идемпотентность вызовов — ещё одно ключевое требование. Любой вызов к микросервису (как прямой, так и компенсирующий) должен быть безопасен для повторного выполнения. Это достигается за счёт использования уникальных идентификаторов запросов (request ID). Сервис, получив запрос с уже известным ID, просто возвращает результат предыдущего выполнения, не выполняя операцию повторно.

Преимущества и недостатки паттерна Saga

Паттерн Saga предоставляет мощный механизм для обеспечения согласованности данных в распределённых системах без жертв в виде производительности и доступности. Он хорошо масштабируется и соответствует принципам микросервисной архитектуры.

Основные преимущества:

  • Отказ от блокирующих распределённых транзакций.
  • Высокая доступность и независимость сервисов.
  • Возможность моделировать сложные, долгоживущие бизнес-процессы.

Основные недостатки:

  • Сложность проектирования и реализации компенсирующих транзакций.
  • Отсутствие строгой изоляции, что требует от бизнес-логики устойчивости к временным несогласованным состояниям.
  • Повышенная сложность отладки и мониторинга, особенно в случае хореографии.

Когда использовать Saga

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

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


Практические аспекты реализации Saga

Пример бизнес-процесса: оформление заказа

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

  1. Сервис заказов создаёт черновик заказа и резервирует его идентификатор.
  2. Сервис инвентаризации проверяет наличие товара на складе и резервирует нужное количество.
  3. Сервис оплаты списывает средства с банковской карты клиента.
  4. Сервис доставки регистрирует заявку на отправку товара.

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

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

Реализация оркестратора

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

Состояние Saga обычно хранится в таблице базы данных с полями:

  • Идентификатор Saga
  • Текущий шаг
  • Статус (в процессе, успешно завершена, отменена)
  • Данные контекста (идентификатор заказа, сумма платежа и т.д.)

При старте нового процесса оркестратор создаёт запись в этой таблице и начинает выполнять шаги. После каждого успешного шага он обновляет текущий шаг в записи. При возникновении ошибки он начинает выполнять компенсирующие шаги в обратном порядке.

Для повышения надёжности рекомендуется использовать шаблон Outbox для отправки сообщений. Это позволяет гарантировать, что сообщение будет отправлено только после успешной фиксации локальной транзакции. Сообщения сначала записываются в специальную таблицу (outbox) в рамках той же транзакции, что и основные данные, а затем фоновый процесс доставляет их получателям.

Обработка временных сбоев

В распределённых системах часто встречаются временные сбои: сетевые проблемы, перегрузка сервисов, таймауты. Saga должна быть устойчива к таким сбоям. Для этого применяются стратегии повторных попыток (retry) с экспоненциальной задержкой. Оркестратор может повторять вызов до тех пор, пока не получит однозначный ответ — успешный или окончательный отказ.

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

Мониторинг и отладка

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

Рекомендуется логировать каждый шаг Saga, включая:

  • Время начала и завершения шага
  • Результат (успех/ошибка)
  • Идентификаторы связанных сущностей (заказ, платёж и т.д.)
  • Текст ошибки в случае сбоя

Эти логи позволяют быстро находить и анализировать проблемы. Кроме того, полезно предоставлять веб-интерфейс для просмотра текущих Saga, их статуса и истории выполнения.

Выбор подхода: хореография или оркестрация?

Выбор между хореографией и оркестрацией зависит от сложности бизнес-процесса и требований к наблюдаемости.

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

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

На практике часто используется гибридный подход: основной процесс управляется оркестратором, а внутри отдельных шагов применяется хореография для взаимодействия с вспомогательными сервисами.

Лучшие практики при работе с Saga

  1. Проектируйте компенсирующие транзакции как полноценные бизнес-операции. Они должны быть идемпотентными, надёжными и соответствовать бизнес-логике.

  2. Используйте уникальные идентификаторы запросов. Это обеспечивает идемпотентность и защищает от дублирования сообщений.

  3. Храните состояние Saga в надёжном хранилище. Это позволяет восстанавливать процесс после сбоев.

  4. Обеспечьте полную трассировку. Каждое событие и операция должны быть связаны с идентификатором Saga.

  5. Предусмотрите механизм ручного вмешательства. Не все сбои можно обработать автоматически, поэтому должна быть возможность вмешательства оператора.

  6. Тестируйте сценарии сбоев. Особенно важно проверять работу компенсирующих транзакций и обработку временных сбоев.

  7. Избегайте длительных блокировок. Saga не должна блокировать ресурсы на всё время своего выполнения. Вместо блокировки используйте резервирование с таймаутом.