Саги та розподілені транзакції
Сага - це довготривала бізнес-транзакція, розбита на послідовність локальних кроків у різних сервісах/сховищах. Кожен крок має компенсуючу дію, яка відкочує ефект кроку при частковому провалі. На відміну від 2PC/3PC, саги не утримують глобальні блокування і підходять для мікросервісів, мульти-регіонів і високих навантажень, де допустима eventual consistency.
1) Коли вибирати саги (а коли - ні)
Підходить:- Тривалі/багатокрокові бізнес-процеси (замовлення → оплата → резерв → доставка).
- Різні домени і сховища, де немає загальної транзакції.
- Потрібна висока доступність і горизонтальне масштабування.
- Тверда ACID-атомарність критична (наприклад, перенесення великих сум у межах одного реєстру).
- Немає чіткої компенсованості (не можна «раз-зарезервувати» або скасувати ефект).
- Юридичні/регуляторні обмеження вимагають суворої ізоляції і «моментального» інваріанта.
2) Моделі саг
1. Оркестрація (Saga Orchestrator): центральний координатор управляє кроками і компенсаціями.
Плюси: явний потік, контроль помилок, спрощена телеметрія.
Мінуси: точка централізації, ризик «товстого» координатора.
2. Хореографія (Choreography): немає центру - кроки ініціюються подіями («сервіс А зробив X → сервіс Б реагує»).
Плюси: слабка зв'язність, просте масштабування.
Мінуси: складніше відстежувати/дебажити потік, ризик «розростання» правил.
3. TCC (Try-Confirm/Cancel): кожен крок - «резервування» (Try), потім підтвердження (Confirm) або скасування (Cancel).
Плюси: ближче до псевдо-двофазного протоколу, керовані ресурси.
Мінуси: дорожче в реалізації інтерфейсів; вимагає таймаутів власників «Try».
3) Проектування кроку і компенсації
Інваріанти: чітко сформулюйте, що повинно бути істинно «до/після» кроку (наприклад, «залишок ≥ 0»).
Компенсація ≠ зворотна транзакція: це логічна дія, що скасовує бізнес-ефект (refund, release, restore).
Ідемпотентність: і крок, і компенсатор повинні безпечно повторюватися (по'operation _ id').
Таймаути: кожен крок має deadline; прострочення ініціює компенсації.
Безповоротні ефекти: фіксуйте їх окремо (повідомлення, e-mail) і допускайте «best effort».
4) Узгодженість і порядок
Eventual consistency: користувачі можуть бачити тимчасові розбіжності; UX - з «очікуванням «/спінерами/статусами.
Порядок за ключем: комутаційні кроки групуйте за бізнес-ключем (order_id), щоб впорядковувати події.
Дедуплікація: зберігайте журнал обробок ('operation _ id'→ статус) з TTL.
5) Транспорт і надійність
Outbox pattern: запис події в локальну таблицю «outbox» всередині тієї ж транзакції, а потім асинхронна публікація в шину.
Inbox/Idempotency store: на стороні споживача - журнал вже оброблених повідомлень.
Exactly-once ефективно: «outbox + idempotent consumer» дає практичне «рівно один раз».
DLQ: для «отруйних» повідомлень з багатою мета-інформацією і безпечним редрайвом.
6) Політики помилок, ретраї, backoff
Повторюємо тільки ідемпотентні кроки; операції запису - з'Idempotency-Key'.
Експоненціальний backoff + джиттер; обмеження спроб і сумарного дедлайну саги.
При системній деградації - Circuit Breaker і graceful degradation (наприклад, скасувати другорядну фіч-частину саги).
Бізнес-конфлікти («409») - повтор після узгодження або компенсувати і завершити.
7) Оркестратор: обов'язки і структура
Функції:- Відстеження стану саги: `PENDING → RUNNING → COMPENSATING → DONE/FAILED`.
- Планування кроків, дедлайни, таймаути, ретраї.
- Роутинг подій і запуск компенсацій.
- Ідемпотентність операцій координатора (журнал команд).
- Спостережуваність: кореляція'saga _ id'в логах/трейсах/метриках.
- Таблиці'saga','saga _ step','commands','outbox'.
- Індекси по'saga _ id','business _ key','status','next _ run _ at'.
8) Хореографія: правила і захист від «снігового кома»
Контракти подій: схеми та версіонування (Avro/Proto/JSON Schema).
Чітка семантика: «подія факту» vs «команда».
Останів ланцюжка: сервіс, виявивши невідповідність, публікує «Failed »/« Compensate» подію.
Сигналізація і алерти на «нескінченні петлі».
9) TCC: Практичні деталі
Try: резерв ресурсу з TTL.
Confirm: фіксація, звільнення тимчасових блокувань.
Cancel: відкат резерву (без побічних ефектів).
Гарbage collection: автоматичний відмін Try після TTL (ідемпотентний Cancel).
Ідемпотентні Confirm/Cancel: повтор безпечний.
10) Приклад (словесна схема) - «Замовлення з оплатою і доставкою»
1. CreateOrder (локально) → outbox: `OrderCreated`.
2. PaymentService: резерв «Try» (TCC); при успіху →'PaymentReserved', при відмові →'PaymentFailed'.
3. InventoryService: резерв товару; при нестачі →'InventoryFailed'.
4. ShippingService: створення слоту доставки (скасовується).
5. Якщо будь-який крок'Failed'→ оркестратор запускає компенсації в зворотному порядку: `CancelShipping` → `ReleaseInventory` → `PaymentCancel`.
6. Якщо все ок →'PaymentConfirm'→'OrderConfirmed'.
11) Псевдокод оркестратора
pseudo startSaga(saga_id, order_id):
steps = [ReservePayment, ReserveInventory, BookShipment, ConfirmPayment]
for step in steps:
res = execWithRetry(step, order_id)
if!res.ok:
compensateInReverse(steps_done(order_id))
return FAIL return OK
execWithRetry(step, key):
for attempt in 1..MAX:
try:
return step.run(key) # идемпотентно catch RetryableError:
sleep(backoff(attempt))
catch NonRetryableError:
return FAIL return FAIL
compensateInReverse(done_steps):
for step in reverse(done_steps):
step.compensate() # идемпотентно
12) Спостережуваність та операційні SLO
Трейсинг: єдиний'saga _ id', анотації'step','attempt','decision'( run/compensate/skip).
Метрики:- Успіх/помилка саг (%), середня тривалість, p95/p99.
- Частка компенсованих саг, топ причин компенсацій.
- Черги/outbox лаги, ретраї по кроках.
- Логи/аудит: рішення оркестратора, ідентифікатори ресурсів, бізнес-ключі.
13) Тестування і хаос
Ін'єкція помилок в кожен крок: таймаути, «5xx», бізнес-конфлікти.
Out-of-order події, дублікати, пропуски (drop).
Довгі хвости латентності → перевірка дедлайнів і компенсацій.
Масові саги → перевірка WFQ/DRR і caps в чергах, відсутність «head-of-line blocking».
Редрайв з DLQ по кроках і по цілій сазі.
14) Мульти-тенантність, регіони, відповідність
Теги'tenant _ id/plan/region'в подіях і сховищах саг.
Residency: дані/події не залишають регіон; крос-регіональні саги проектуйте як федерації локальних саг + агрегуючі події.
Пріоритизація: VIP-саги мають більшу квотну вагу; ізоляція воркерів per tenant.
15) Чек-лист перед продом
- У кожного кроку є чіткий компенсатор, обидва - ідемпотентні.
- Обрано шаблон: оркестрація/хореографія/ТСС; описані межі відповідальності.
- Outbox/Inbox впроваджені, дедуплікація по'operation _ id'.
- Політики ретраїв: backoff з джитером, ліміти спроб і загальний дедлайн саги.
- Контракти подій версіоновані, є валідація схеми.
- DLQ і безпечний редрайв налаштовані.
- Телеметрія: метрики, трейсинг, кореляція'saga _ id'.
- Операційні playbooks: ручний cancel/force-confirm, розшивка «завислих» саг.
- Тести хаосу і навантаження проходять, SLO/бюджет помилок визначені.
16) Типові помилки
Немає компенсатора або він «нечистий» (має побічні ефекти).
Відсутня ідемпотентність/дедуп - дублі і «гойдалки» станів.
«Сага в сазі» без явних кордонів - цикли і взаємні блокування.
Немає дедлайнів → «вічні» саги і витоки ресурсів.
Оркестратор зберігає стан «в пам'яті» без стійкого стору.
Хореографія без центру телеметрії → «невидимі» збої.
Непрозорий UX: користувачі не бачать проміжних статусів.
17) Швидкі рецепти
Класика SaaS: оркестрація + outbox/inbox, експоненціальний backoff, DLQ, статуси саги в UI.
Сильні інваріанти на ресурс: TCC з TTL резерву і GC Cancel.
Високий обсяг/навантаження: хореографія подій + сувора ідемпотентність і метрики за ключем.
Мульти-регіон: локальні саги + фінальні агрегати; уникати глобальних блокувань.
Висновок
Саги - це спосіб отримати передбачувану узгодженість в розподілених системах без глобальних блокувань. Чіткі компенсатори, ідемпотентність, надійна доставка (outbox/inbox), дисципліна таймаутів і ретраїв, плюс телеметрія і плейбуки - ключ до того, щоб складні бізнес-процеси залишалися стійкими і читаємими при зростанні навантаження, числа сервісів і географій.