Question 4 · Section 17

What are compensating transactions

A compensating transaction is an action that undoes the result of a previous successful transaction in a Saga.

Language versions: English Russian Ukrainian

Junior Level

A compensating transaction is an action that undoes the result of a previous successful transaction in a Saga.

Important: the original transaction is already committed and visible to others. A compensation is not a ROLLBACK — it’s a new transaction that does the opposite.

In microservices you cannot do a ROLLBACK like in a regular database. Instead, you need to manually undo each step.

Saga:
1. ✅ Create order
2. ✅ Charge money
3. ❌ Reserve product (FAIL!)

Compensating transactions (in reverse order):
2. ✅ Refund money (compensation for step 2)
1. ✅ Cancel order (compensation for step 1)

Middle Level

Rules of compensating transactions

1. Executed in reverse order:

Steps: A -> B -> C -> D (fail)
Compensations: compensate(C) -> compensate(B) -> compensate(A)

2. Must be idempotent:

public void refundPayment(String paymentId) {
    if (paymentRepository.isRefunded(paymentId)) {
        return;  // Already refunded — do nothing
    }
    paymentRepository.refund(paymentId);
    paymentRepository.markRefunded(paymentId);
}

3. May not be exact opposites:

// Step: "Reserve product"
// Compensation: NOT "remove reservation", but "set status to available"

// Step: "Send email"
// Compensation: NOT "delete email", but "send cancellation email"

Common mistakes

  1. Missing compensation:
    Order -> Payment -> Inventory (fail)
    Compensation: refund(Payment) ... forgot to cancel(Order)!
    Result: order created, money refunded, but order is not cancelled
    

Senior Level

Architectural Trade-offs

Semantic compensation vs technical undo:

Technical undo = DELETE from table.
Semantic compensation = sending email to client "your order is cancelled" + awarding bonus points.

Semantic is better for the business, but more complex to implement

Edge Cases

1. Compensation failure:

// If compensation also fails — retry
// Simplified example — in production add exponential backoff and jitter.
// A simple loop retry can overwhelm a failing service.
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);
        }
    }
    // After all retries — manual intervention
    alertService.sendAlert("Compensation failed after retries", step);
}

2. Non-compensatable steps:

Some steps cannot be undone:
- Email sent — cannot "take it back"
- SMS sent — cannot cancel
- Physical product already shipped

Solution: mark as non-compensatable and alert for manual fix

Production Experience

Saga with compensations:

public class OrderSaga {

    public void onPaymentFailed(PaymentFailedEvent event) {
        // Compensations in reverse order
        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 or 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

✅ Compensations in reverse order
✅ Idempotency for each compensation
✅ Retry for compensations
✅ Alert on compensation failure
✅ Log every compensation step

❌ Don't forget compensations
❌ Don't ignore compensation failures
❌ Don't assume compensation will always work

Interview Cheat Sheet

Must know:

  • A compensating transaction is NOT a ROLLBACK — it’s a new transaction with the opposite action
  • Compensations are executed in reverse order (C->B->A)
  • Each compensation must be idempotent
  • Semantic compensation is better than technical undo (cancellation email vs DELETE)
  • Some steps are non-compensatable (email sent, product shipped)
  • On compensation failure — retry + alert for manual intervention
  • Compensation may not be the exact opposite of the step

Common follow-up questions:

  • What if the compensation also fails? Retry with exponential backoff, then alert for manual fix.
  • Which steps cannot be undone? Email/SMS sending, actual product shipment — mark as non-compensatable.
  • Why is idempotency needed for compensations? A compensation may be called twice (retry, duplicate event).
  • Semantic vs technical compensation? Technical = DELETE from table, semantic = award bonus points + notify client.

Red flags (DO NOT say):

  • “Compensation = ROLLBACK” — no, the original transaction is already committed
  • “Compensation is always possible” — no, you can’t “take back” an email
  • “You can compensate in any order” — no, strictly in reverse
  • “Idempotency is not important for compensations” — critically important

Related topics:

  • [[1. What is Saga pattern and when to use it]]
  • [[2. What is the difference between choreography and orchestration in Saga]]
  • [[3. How to implement distributed transactions in microservices]]
  • [[19. What is Retry pattern and how to use it correctly]]
  • [[17. How to ensure fault tolerance of microservices]]