Сага-паттерн и распределенные транзакции
Сага-паттерн и распределенные транзакции
1) Зачем нужны саги
Классический 2PC (двухфазная фиксация) плохо масштабируется, сложен под отказами и блокирует ресурсы. Сага разбивает общий бизнес-процесс на последовательность локальных транзакций (шагов), каждая из которых коммитится независимо. При сбое последующие шаги отменяются, а уже выполненные — компенсируются обратными операциями.
Результат: управляемая eventual consistency без глобальной блокировки, высокая живучесть и четкий протокол восстановления.
2) Базовые модели
2.1 Оркестрация
Выделенный координатор саги управляет шагами: посылает команды, ждет ответы/события, инициирует компенсации.
Плюсы: централизованный контроль, простая наблюдаемость, явные дедлайны. Минусы: дополнительный компонент.
2.2 Хореография
Нет координатора; сервисы реагируют на события друг друга (“OrderPlaced” → “PaymentCaptured” → “InventoryReserved” …).
Плюсы: слабая связность. Минусы: сложнее прослеживать, риск «танца смерти» без четких правил.
2.3 TCC (Try-Confirm/Cancel)
Вариант с «заморозкой» ресурсов:1. Try — подготовка/резерв,
2. Confirm — фиксация,
3. Cancel — откат.
Гарантии выше, но сложнее контракты и таймауты резервов.
3) Контракты шагов и компенсации
Каждый шаг = локальная транзакция + компенсация (идемпотентная, допускает повтор).
Компенсация не обязана полностью «вернуть мир» — достаточно доменной эквивалентности (например, «выплата возврата» вместо «удалить платеж»).
Определите инварианты: для денег — баланс не уходит в минус; для заказов — нет «зависших» статусов.
Вводите дедлайны/TTL резервов и «garbage collector» для просроченных попыток.
4) Согласованность и семантики доставки
Доставка сообщений: at-least-once (дефолт) → все операции должны быть идемпотентными.
Порядок: важен по корреляционному ключу (например, `order_id`, `player_id`).
Exactly-once — не цель саги; добиваемся эффективной ровно-однократности через идемпотентные ключи, outbox/inbox и корректное коммитирование.
5) Состояние саги и ее лог
Что хранить:- `saga_id`, `correlation_id`, текущий статус (Running/Completed/Compensating/Compensated/Failed),
- шаг и его переменные (IDs платежей/резервов),
- историю (журнал) событий/решений, таймстемпы, дедлайны, число ретраев.
- Отдельный Saga Store (таблица/документ), доступный координатору.
- Для хореографии — локальные «агенты» саги, публикующие события статуса в общий топик.
6) Паттерны надежной публикации: outbox/inbox
Outbox: шаг коммитит изменения и записывает событие/команду в таблицу outbox в одной транзакции; воркер публикует в шину.
Inbox: потребитель ведет таблицу обработанных `message_id` → дедуп + идемпотентность.
После успешного побочного эффекта коммитим offset/ACK (Kafka/RabbitMQ) — не раньше.
7) Проектирование шагов саги
7.1 Пример (покупка в iGaming/e-commerce)
1. PlaceOrder → статус `PENDING`.
2. AuthorizePayment (Try) → `payment_hold_id`.
3. ReserveInventory → `reservation_id`.
4. CapturePayment (Confirm).
5. FinalizeOrder → `COMPLETED`.
- если (3) провалился → `CancelPaymentHold`;
- если (4) провалился после (3) → `ReleaseInventory`;
- если (5) провалился → `RefundPayment` и `ReleaseInventory`.
7.2 Дедлайны/ретраи
Максимум N ретраев с экспоненциальной задержкой + джиттер.
После превышения — переход в `Compensating`.
Храните next_attempt_at и deadline_at для каждого шага.
8) Оркестратор vs платформа
Варианты:- Легкий домашний оркестратор (микросервис + таблица Saga).
- Платформы: Temporal/Cadence, Camunda, Netflix Conductor, Zeebe — дают таймеры, ретраи, долгоживущие воркфлоу, видимость и веб-консоль.
- Для хореографии используйте каталог событий и строгое соглашение о статусах/ключах.
9) Протоколы интеграции
9.1 Асинхронно (Kafka/RabbitMQ)
Команды: `payments.authorize.v1`, `inventory.reserve.v1`.
События: `payments.authorized.v1`, `inventory.reserved.v1`, `payments.captured.v1`, `payments.refunded.v1`.
Ключ партиции = `order_id`/`player_id` для порядка.
9.2 Синхронно (HTTP/gRPC) внутри шага
Допустимо для «коротких» шагов, но всегда с таймаутами/ретраями/идемпотентностью и fallback в асинхронную компенсацию.
10) Идемпотентность и ключи
В запросах команд и компенсаций передавайте `idempotency_key`.
Побочные эффекты (запись в БД/списание) выполняются условно: «выполнить если еще не видели `idempotency_key`».
Компенсации тоже идемпотентны: повтор `RefundPayment(id=X)` безопасен.
11) Обработка ошибок
Классы:- Transient (сети/таймауты) → ретраи/backoff.
- Business (недостаточно средств, лимиты) → немедленная компенсация/альтернативный путь.
- Irrecoverable (нарушение инварианта) → ручное вмешательство, «ручная» компенсация.
- Выстраивайте матрицу решений: тип ошибки → действие (retry/compensate/escalate).
12) Наблюдаемость и SLO саг
SLI/SLO:- End-to-end latency саги (p50/p95/p99).
- Success rate (доля завершенных без компенсаций).
- Mean time to compensate и compensation rate.
- Orphaned sagas (висящие) и время до GC.
- Трассировка: `trace_id`/`saga_id` как span link между шагами; метрики burn-rate для бюджетов ошибок.
Логи: каждая смена статуса саги = структурированная запись с причиной.
13) Примеры (псевдокод)
13.1 Оркестратор (идея)
python def handle(OrderPlaced e):
saga = Saga. start(e. order_id)
saga. run(step=authorize_payment, compensate=cancel_payment)
saga. run(step=reserve_inventory, compensate=release_inventory)
saga. run(step=capture_payment, compensate=refund_payment)
saga. run(step=finalize_order, compensate=refund_and_release)
saga. complete()
def run(step, compensate):
try:
step () # local transaction + outbox except Transient:
schedule_retry()
except Business as err:
start_compensation(err)
13.2 Outbox (идея таблицы)
outbox(id PK, aggregate_id, event_type, payload, created_at, sent_at NULL)
inbox(message_id PK, processed_at, status)
saga(order_id PK, state, step, next_attempt_at, deadline_at, context JSONB)
saga_log(id PK, order_id, time, event, details)
13.3 Хореография (идеи тем)
`orders.placed` → потребители: `payments.authorize`, `inventory.reserve`
`payments.authorized` + `inventory.reserved` → `orders.try_finalize`
Любой отказ → `orders.compensate` → инициируются `payments.cancel/refund`, `inventory.release`.
14) Сравнение с 2PC и ES
2PC: сильная согласованность, но блокировки, узкие места, «медные трубы».
Сага: eventual consistency, нужна дисциплина компенсаций и телеметрии.
Event Sourcing: хранит события как источник истины; саги на нем естественны, но добавляют сложность миграций/снапшотов.
15) Безопасность и комплаенс
Секьюрность транспорта (TLS/mTLS), ACL per topic/queue.
В событиях — минимум PII, шифрование чувствительных полей, токенизация.
Аудит доступа к сагам и журналам компенсаций.
SLA с внешними провайдерами (платежи/доставка) = параметры дедлайнов и лимитов ретраев.
16) Чек-лист внедрения (0–45 дней)
0–10 дней
Выделите процессы-кандидаты (мультисервисные, с компенсацией).
Выберите модель (оркестрация/хореография/TCC) и корреляционный ключ.
Опишите шаги/компенсации, инварианты и дедлайны. Поднимите таблицы `saga`, `outbox`, `inbox`.
11–25 дней
Включите outbox/inbox, идемпотентность и ретраи с backoff.
Деплойте первые саги; добавьте дашборды SLI/SLO и трассировку.
Напишите runbook компенсаций (в т.ч. ручных) и эскалаций.
26–45 дней
Автоматизируйте GC «висящих» саг, периодические перезапуски/продолжения по дедлайну.
Проведите game-day: отказ шага, превышение дедлайна, недоступность брокера.
Стандартизируйте контракты событий (версии, совместимость), заведите «каталог саг».
17) Анти-паттерны
«Компенсация = delete из БД» вместо доменно корректного обратного действия.
Нет outbox/inbox → потеря событий/двойные эффекты.
Ретраи без джиттера → само-DDoS зависимостей.
Ожидание сильной согласованности на чтении без «идет обработка…».
Один гигантский оркестратор на все → монолит управления.
Тотальная хореография без видимости и SLA → неуправляемый танец.
Игнорирование дедлайнов → вечные резервы/холды.
18) Метрики зрелости
≥ 90% критичных процессов покрыты сагами/компенсациями и имеют описанные инварианты.
Outbox/inbox интегрированы для всех продьюсеров/консьюмеров Tier-0/1.
SLO: p95 end-to-end саги в норме, success rate стабильный, orphaned < целевого.
Прозрачная трассировка и дашборды «по шагам», burn-rate алерты.
Ежеквартальный game-day и проверка ручных runbook-компенсаций.
19) Заключение
Сага — это практичный контракт согласованности для распределенных систем: четкие шаги и обратные действия, дисциплина публикации (outbox/inbox), дедлайны и ретраи, наблюдаемость и процессы компенсации. Выберите модель (оркестрация/хореография/TCC), зафиксируйте инварианты и ключи, сделайте обработчики идемпотентными — и ваши мультисервисные бизнес-процессы станут предсказуемыми и устойчивыми без дорогого 2PC.