Вопрос 4 · Раздел 17

Что такое компенсирующие транзакции

не ROLLBACK, а новая транзакция, которая делает противоположное действие.

Версии по языкам: English Russian Ukrainian

🟢 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 об отмене"

Типичные ошибки

  1. Забытая компенсация:
    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. Как обеспечить отказоустойчивость микросервисов]]