Что такое компенсирующие транзакции
не 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. Как обеспечить отказоустойчивость микросервисов]]