Що таке компенсувальні транзакції
не ROLLBACK, а нова транзакція, яка робить протилежну дію.
🟢 Junior Level
Компенсувальна транзакція — це дія, яке скасовує результат попередньої успішної транзакції в Saga.
Важливо: оригінальна транзакція вже закоммічена і видна іншим. Компенсація — це не ROLLBACK, а нова транзакція, яка робить протилежну дію.
У мікросервісах неможливо зробити ROLLBACK як у звичайній БД. Замість цього потрібно вручну скасувати кожен крок.
Saga:
1. ✅ Створити замовлення
2. ✅ Списати гроші
3. ❌ Зарезервувати товар (FAIL!)
Компенсувальні транзакції (у зворотному порядку):
2. ✅ Повернути гроші (компенсація кроку 2)
1. ✅ Скасувати замовлення (компенсація кроку 1)
🟡 Middle Level
Правила компенсувальних транзакцій
1. Виконуються у зворотному порядку:
Кроки: A → B → C → D (fail)
Компенсації: compensate(C) → compensate(B) → compensate(A)
2. Мають бути ідемпотентними:
public void refundPayment(String paymentId) {
if (paymentRepository.isRefunded(paymentId)) {
return; // Уже повернуто — не робимо нічого
}
paymentRepository.refund(paymentId);
paymentRepository.markRefunded(paymentId);
}
3. Можуть не бути точною протилежністю:
// Крок: "Зарезервувати товар"
// Компенсація: НЕ "зняти резерв", а "перевести в статус available"
// Крок: "Надіслати email"
// Компенсація: НЕ "видалити email", а "надіслати email про скасування"
Типові помилки
- Забута компенсація:
Order → Payment → Inventory (fail) Компенсація: refund(Payment) ... забули cancel(Order)! Підсумок: замовлення створене, гроші повернуті, але замовлення не скасоване
🔴 Senior Level
Архітектурні Trade-offs
Semantic compensation vs technical undo:
Technical undo = DELETE з таблиці.
Semantic compensation = надсилання email клієнту «ваше замовлення скасовано» + нарахування бонусних балів.
Semantic краще для бізнесу, але складніше реалізувати
Edge Cases
1. Compensation failure:
// Якщо компенсація теж впала — retry
// Спрощений приклад — у production додайте exponential backoff і jitter.
// Простий loop retry може «задовбити» падаючий сервіс.
public void compensateWithRetry(SagaStep step, int maxRetries) {
for (int i = 0; i < maxRetries; i++) {
try {
step.compensate();
return;
} catch (Exception e) {
log.warn("Compensation retry {}", i, e);
}
}
// Після усіх retry — manual intervention
alertService.sendAlert("Compensation failed after retries", step);
}
2. Non-compensatable steps:
Деякі кроки не можна скасувати:
- Email надіслано — не можна "забрати"
- SMS надіслано — не можна скасувати
- Реальний товар вже shipped
Рішення: позначити як non-compensatable і alert для manual fix
Production Experience
Saga з компенсаціями:
public class OrderSaga {
public void onPaymentFailed(PaymentFailedEvent event) {
// Компенсації у зворотному порядку
compensateInventory(event.orderId());
compensateOrder(event.orderId());
}
private void compensateInventory(String orderId) {
try {
inventoryClient.releaseReservation(orderId);
} catch (Exception e) {
log.error("Inventory compensation failed", e);
alertService.sendAlert("Inventory compensation failed", orderId);
// Retry або manual intervention
}
}
private void compensateOrder(String orderId) {
try {
orderClient.cancelOrder(orderId);
} catch (Exception e) {
log.error("Order compensation failed", e);
alertService.sendAlert("Order compensation failed", orderId);
}
}
}
Best Practices
✅ Компенсації у зворотному порядку
✅ Ідемпотентність кожної компенсації
✅ Retry для компенсацій
✅ Alert при failure компенсації
✅ Логуйте кожен крок компенсації
❌ Не забувайте компенсації
❌ Не ігноруйте failure компенсацій
❌ Не сподівайтесь що компенсація завжди спрацює
🎯 Шпаргалка для співбесіди
Обов’язково знати:
- Компенсувальна транзакція — це НЕ ROLLBACK, а нова транзакція з протилежною дією
- Компенсації виконуються у зворотному порядку (C→B→A)
- Кожна компенсація має бути ідемпотентною
- Semantic compensation краща за technical undo (email про скасування vs DELETE)
- Деякі кроки non-compensatable (email надіслано, товар shipped)
- При failure компенсації — retry + alert для manual intervention
- Компенсація може не бути точною протилежністю кроку
Часті уточнюючі питання:
- Що якщо компенсація теж впала? Retry з exponential backoff, потім alert для manual fix.
- Які кроки не можна скасувати? Email/SMS надсилання, реальне відвантаження товару — позначити як non-compensatable.
- Навіщо ідемпотентність компенсацій? Компенсація може викликатись двічі (retry, duplicate event).
- Semantic vs technical compensation? Technical = DELETE з таблиці, semantic = нарахувати бонуси + сповістити клієнта.
Червоні прапорці (НЕ говорити):
- “Компенсація = ROLLBACK” — ні, оригінальна транзакція вже закоммічена
- “Компенсація завжди можлива” — ні, email не можна “забрати”
- “Можна компенсувати в будь-якому порядку” — ні, строго у зворотному
- “Ідемпотентність не важлива для компенсацій” — критично важлива
Пов’язані теми:
- [[1. Що таке патерн Saga і коли його використовувати]]
- [[2. У чому різниця між хореографією та оркестрацією в Saga]]
- [[3. Як реалізувати розподілені транзакції в мікросервісах]]
- [[19. Що таке патерн Retry і як його правильно використовувати]]
- [[17. Як забезпечити відмовостійкість мікросервісів]]