What are compensating transactions
A compensating transaction is an action that undoes the result of a previous successful transaction in a Saga.
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
- 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]]