Прямой ответ
Компенсация — действие, отменяющее эффект ранее выполненного шага. Она нужна потому, что в распределённом процессе нет общей транзакции: шаги выполняются в разных системах, и откат одной транзакцией невозможен.
Ключевое различие:
| Транзакционный откат | Компенсация | |
|---|---|---|
| Механизм | Отмена незафиксированных изменений | Новое действие, устраняющее эффект |
| Видимость промежуточного состояния | Отсутствует | Присутствует |
| Применяется к | Одной базе данных | Разным системам |
| Результат | Исходное состояние | Состояние, эквивалентное исходному |
Компенсация не восстанавливает исходное состояние, а приводит к эквивалентному. Списание денег отменяется возвратом, а не удалением записи о списании — запись остаётся, и это правильно для аудита; о механизме — в статье про сагу и outbox.
Структура процесса с компенсацией:
completed = []
try:
completed.append(("charge", charge_payment(order_id)))
completed.append(("reserve", reserve_stock(order_id)))
completed.append(("ship", create_shipment(order_id)))
except StepFailed as e:
for name, result in reversed(completed):
COMPENSATIONS[name](result)
raise
Два свойства реализации обязательны.
1. Обратный порядок. Компенсация выполняется от последнего выполненного шага к первому. Отмена списания до отмены резерва товара оставляет товар зарезервированным без оплаченного заказа.
2. Журнал выполненных шагов. Список completed должен сохраняться, а не существовать только в памяти. При сбое процесса во время компенсации нужно знать, какие шаги уже отменены; о сохранении состояния — в статье про workflow engine.
Главная сложность — необратимые действия. Отправленное письмо нельзя отозвать, а созданный документ у третьего лица нельзя удалить. Для таких шагов компенсация заменяется уведомлением:
шаг необратим -> компенсация = уведомление клиента и оператора
Проектирование процесса учитывает это: необратимые шаги размещаются последними, чтобы до них процесс мог быть отменён обычной компенсацией.
Что делает компенсацию сложной
Четыре свойства распределённого процесса усложняют отмену шагов по сравнению с откатом транзакции.
Промежуточное состояние видно другим. Между выполнением шага и его компенсацией товар зарезервирован, деньги списаны, уведомление отправлено. Другие участники системы видят это состояние и могут на него отреагировать.
Задержка между шагом и отменой. Отмена может произойти через часы. За это время состояние внешней системы изменится: зарезервированный товар мог быть частично отпущен, а возврат станет неприменим.
Сбой во время компенсации. Отмена нескольких шагов — тоже последовательность, которая может прерваться. Процесс остаётся в частично отменённом состоянии, требующем продолжения.
Отсутствие общего атомарного действия. Каждый шаг отменяется отдельным вызовом в отдельной системе. Гарантии, что отменятся все шаги, не существует — только механизм продолжения после сбоя.
Эти свойства определяют требования к реализации: сохраняемый журнал шагов, идемпотентность отмены, возможность продолжения после сбоя и метрика незавершённых компенсаций.
Отдельный вопрос — выбор между компенсацией и предотвращением. Проверка предусловий до выполнения шага снижает потребность в отмене: если резерв товара проверяется до списания денег, отменять списание не потребуется.
Практика: реализация компенсации
1. Описывайте компенсацию для каждого шага при проектировании. Шаг без компенсации либо необратим, либо его отмена не продумана. Оба случая требуют явного решения.
2. Выполняйте компенсацию в обратном порядке. Прямой порядок оставляет промежуточные эффекты, которые не устраняются.
3. Делайте компенсацию идемпотентной. Повтор отмены при сбое не должен выполнять возврат дважды.
4. Сохраняйте журнал выполненных шагов. Восстановление после сбоя требует знания, что выполнено и что отменено.
5. Размещайте необратимые шаги последними. До отправки уведомления процесс отменяется компенсацией, после — только уведомлением об отмене.
6. Обрабатывайте сбой самой компенсации. Отмена может не выполниться, и процесс остаётся в частично отменённом состоянии. Это требует тревоги и ручного вмешательства.
7. Проверяйте полноту компенсации. Метрика числа процессов в состоянии частичной отмены показывает незавершённые случаи.
8. Фиксируйте компенсацию в журнале. Отмена — событие, которое нужно видеть при разборе обращения клиента.
Рекомендации:
- Различайте технический откат и семантический. Удаление записи и возврат денег — разные операции, и для аудита нужна вторая.
- Не компенсируйте то, что можно не выполнять. Проверка предусловий до выполнения шага снижает потребность в отмене.
- Учитывайте задержку между шагом и компенсацией. За часы между ними состояние внешней системы может измениться, и отмена станет неприменимой.
- Тестируйте сбой на каждом шаге. Отказ первого, среднего и последнего шага даёт разные сценарии компенсации.
- Документируйте необратимые шаги. Список действий, которые нельзя отменить, нужен при проектировании процесса.
Практический сценарий: частично отменённый заказ после сбоя компенсации
Контекст: при отказе шага создания отгрузки процесс выполнял компенсацию. После сбоя исполнителя часть заказов осталась с возвращёнными деньгами и зарезервированным товаром.
- Диагностика. Компенсация выполнялась в памяти процесса: список выполненных шагов не сохранялся. После перезапуска исполнителя процесс начался заново, и часть компенсаций не была выполнена.
- Причина. Отсутствие сохраняемого журнала выполненных шагов и отсутствие повторного запуска компенсации после сбоя. Метрики незавершённых компенсаций не было, поэтому случаи обнаружились по обращениям клиентов.
- Действия. (а) Журнал выполненных шагов перевели на хранение в базе в той же транзакции, что и сам шаг; (б) компенсацию сделали идемпотентной и возобновляемой после сбоя; (в) добавили метрику числа процессов в состоянии частичной отмены с тревогой; (г) проверили существующие заказы и завершили отложенные компенсации; (д) добавили тесты на сбой каждого шага с проверкой полноты отмены.
- Результат. Сбой исполнителя больше не прерывает компенсацию, а незавершённые отмены обнаруживаются метрикой вместо обращений клиентов.
Вывод: компенсация требует сохраняемого журнала выполненных шагов и возможности продолжения после сбоя. Компенсация в памяти процесса прерывается вместе с ним и оставляет систему в частично отменённом состоянии, которое обнаруживается только по обращениям.
FAQ
Всегда ли возможна компенсация? Нет. Отправленное уведомление и созданный документ у третьей стороны не отменяются. Для таких шагов компенсация заменяется уведомлением.
В каком порядке выполнять компенсацию? В обратном к выполнению. Прямой порядок оставляет промежуточные эффекты неустранёнными.
Что делать, если компенсация не выполнилась? Фиксировать состояние и поднимать тревогу. Процесс в частично отменённом состоянии требует ручного вмешательства.
Чем компенсация отличается от отката транзакции? Компенсация — новое действие, устраняющее эффект, а не отмена незафиксированных изменений. Промежуточное состояние при этом видно другим участникам.
Источники: microservices.io (Saga), Azure Architecture Center (Saga), Temporal docs — ссылки в шапке.