Що таке патерн Saga і коли його використовувати
У моноліті ACID-транзакція гарантує, що всі зміни або збережуться, або відкотяться разом. У мікросервісах дані розкидані по різних БД, і немає механізму, який атомарно змінить у...
🟢 Junior Level
Saga — це патерн для керування розподіленими транзакціями в мікросервісах.
У моноліті ACID-транзакція гарантує, що всі зміни або збережуться, або відкотяться разом. У мікросервісах дані розкидані по різних БД, і немає механізму, який атомарно змінить усе одразу.
У моноліті є одна база даних і звичайні транзакції (ACID). У мікросервісах у кожного сервісу своя БД, і неможливо зробити одну транзакцію на кілька сервісів.
Saga вирішує цю проблему:
- Розбиває велику транзакцію на послідовність локальних транзакцій
- Кожна локальна транзакція — у своєму сервісі
- Якщо щось пішло не так — виконуються компенсувальні транзакції (відкат)
Приклад — замовлення в інтернет-магазині:
1. Order Service: створити замовлення
2. Payment Service: списати гроші
3. Inventory Service: зарезервувати товар
4. Shipping Service: відправити
Якщо Payment впав → скасувати замовлення (компенсація)
Якщо Inventory впав → повернути гроші + скасувати замовлення
// Що бачить користувач: замовлення створено → оплату пройдено → інвентар зарезервовано.
// Якщо Shipping впав: compensation скасовує все, користувач бачить "замовлення скасовано".
// Eventual consistency зазвичай займає секунди-хвилини.
🟡 Middle Level
Два типи Saga
1. Choreography (Хореографія):
Кожен сервіс публікує подію → інші сервіси слухають і реагують
Order Service → "OrderCreated" → Payment Service
Payment Service → "PaymentCompleted" → Inventory Service
Inventory Service → "InventoryReserved" → Shipping Service
2. Orchestration (Оркестрація):
Центральний оркестратор керує всім процесом
Orchestrator
/ | \ \
Order Payment Inventory Shipping
Коли використовувати
✅ Saga підходить коли:
- Транзакція залучає кілька сервісів
- Потрібна eventual consistency (не миттєва узгодженість)
- Немає єдиної БД
❌ Якщо бізнес-вимоги юридично вимагають строгої ACID (банківські перекази), Saga може не підійти без додаткових гарантій (блокування, двофазний комміт).
Типові помилки
- Забута компенсація:
Order → Payment → Inventory (fail) Потрібно: refund(payment) + cancel(order) Якщо забули refund → гроші списані, замовлення немає
🔴 Senior Level
Internal Implementation
Saga log:
Кожна Saga повинна логувати:
1. Розпочаті кроки
2. Завершені кроки
3. Компенсації
Це дозволяє відновити стан після рестарту
Архітектурні Trade-offs
| Хореографія | Оркестрація |
|---|---|
| Немає єдиної точки відмови | Оркестратор — single point of failure |
| Складно відстежити потік | Легко моніторити |
| Циклічні залежності | Немає циклічних залежностей |
| Добре для простих Saga | Добре для складних Saga |
Edge Cases
1. Idempotency:
Компенсувальна транзакція може викликатись двічі
→ Потрібна ідемпотентність
refund(paymentId):
if alreadyRefunded(paymentId): return
doRefund(paymentId)
markRefunded(paymentId)
2. Partial failure:
Saga: A → B → C (fail)
Компенсації: compensate(B) → compensate(A)
Якщо compensate(B) теж впала → retry + alert
Production Experience
Order Saga:
@SagaDefinition
public class OrderSaga {
@StartSaga
@SagaEventHandler(associationProperty = "orderId")
public void handle(OrderCreatedEvent event) {
SagaLifecycle.start();
commandGateway.send(new ReserveInventoryCommand(event.orderId()));
}
@SagaEventHandler(associationProperty = "orderId")
public void handle(InventoryReservedEvent event) {
commandGateway.send(new ProcessPaymentCommand(event.orderId()));
}
@SagaEventHandler(associationProperty = "orderId")
public void handle(PaymentCompletedEvent event) {
commandGateway.send(new ShipOrderCommand(event.orderId()));
SagaLifecycle.end();
}
@SagaEventHandler(associationProperty = "orderId")
public void handle(InventoryReservationFailedEvent event) {
// Компенсація — скасувати замовлення
commandGateway.send(new CancelOrderCommand(event.orderId()));
SagaLifecycle.end();
}
}
Best Practices
✅ Логуйте кожен крок Saga
✅ Реалізуйте idempotency для компенсацій
✅ Використовуйте timeouts для кожного кроку
✅ Моніторьте затримки між кроками
❌ Не використовуйте для простих CRUD операцій
❌ Не забувайте про компенсувальні транзакції
❌ Не ігноруйте partial failures
🎯 Шпаргалка для співбесіди
Обов’язково знати:
- Saga — патерн для розподілених транзакцій без єдиної БД
- Два типи: Choreography (event-driven) та Orchestration (coordinator-driven)
- Компенсувальні транзакції скасовують кроки у зворотному порядку
- Eventual consistency — дані узгоджені не миттєво
- Idempotency критична для компенсацій
- Transactional Outbox гарантує доставку подій
- Хореографія — 2-3 сервіси, Оркестрація — 5+ сервісів
- Saga timeout захищає від транзакцій, що зависли
Часті уточнюючі питання:
- Чим Saga відрізняється від 2PC? Saga не блокує ресурси, працює з гетерогенними БД, але дає eventual consistency замість строгої ACID.
- Що буде якщо компенсація теж впаде? Retry + alert для manual intervention.
- Коли НЕ використовувати Saga? Для простих CRUD-операцій в одному сервісі або коли потрібна строга ACID (банківські перекази).
- Як моніторити Saga? Логуйте кожен крок, відстежуйте тривалість між кроками, використовуйте distributed tracing.
Червоні прапорці (НЕ говорити):
- “Saga гарантує ACID” — ні, eventual consistency
- “Компенсація = ROLLBACK” — ні, це нова транзакція
- “Хореографія краща для 10 сервісів” — ні, оркестрація
- “Saga не потрібна якщо є Kafka” — Kafka лише транспорт, логіка все одно потрібна
Пов’язані теми:
- [[2. У чому різниця між хореографією та оркестрацією в Saga]]
- [[3. Як реалізувати розподілені транзакції в мікросервісах]]
- [[4. Що таке компенсувальні транзакції]]
- [[13. Що таке патерн Database per Service]]
- [[22. Що таке distributed tracing]]