Идемпотентность
Идемпотентная операция — операция, повторное выполнение которой даёт тот же результат, что и единственное. Применительно к обработке сообщений: «обработать одно и то же сообщение N раз = обработать его один раз». Никаких дублирующих документов, повторных списаний, разъезжающихся остатков.
В модели at-least-once идемпотентность обработчика — не пожелание, а обязательное требование. Без неё обмен гарантированно сломается при первом же сбое между «приняли сообщение» и «отправили ACK».
Почему дубликаты возможны
DataHUB фиксирует подтверждение доставки после записи сообщения в локальный пул приёмника (см. Жизненный цикл сообщения). Сценарий, в котором возникает дубликат:
- Шина выдала сообщение с идентификатором
Xприёмнику. - Приёмник записал его в свой локальный пул, транзакция закоммитилась.
- Приёмник пытается отправить шине ACK — в этот момент сеть оборвалась, либо процесс упал.
- Шина ACK не получила → считает сообщение недоставленным → через таймаут выдаёт его снова.
- Приёмник видит сообщение с идентификатором
Xвторой раз — в его пуле такая запись уже есть.
Этот сценарий не редкий: он возникает при любых сетевых неприятностях, при перезагрузке приёмника в момент приёма, при ручной повторной выдаче администратором. Закладываться надо сразу.
Где нужна идемпотентность
Из-за двухфазной модели приёмника (приём в пул → отложенная обработка) защита от дубликатов нужна в двух разных местах.
На фазе приёма. Шина может повторно выдать сообщение, если ACK не пришёл. Приём должен корректно отрабатывать запись дубликата в локальный пул — повторная попытка вставки с тем же идентификатором распознаётся и игнорируется, не приводит ко второй копии в пуле.
На фазе прикладной обработки. Одно и то же сообщение из пула может быть обработано несколько раз — при ретрае из-за ненайденной ссылки, при сбое посередине, при ручном переоткате администратором. Обработчик должен распознавать «эта запись уже превратилась в документ» и не создавать второй.
Ключ к идемпотентности — стабильный идентификатор
Идемпотентность работает потому, что у сообщения есть уникальный идентификатор, не меняющийся между попытками доставки. Это одно и то же логическое сообщение, не «копия» — именно по этому идентификатору приёмник распознаёт повтор.
Идентификатор присваивается на стороне источника при формировании сообщения и сохраняется на всём пути — см. Жизненный цикл сообщения / Сквозной идентификатор.
Естественно идемпотентные операции
Не все обработки требуют отдельной защиты от дубликатов. Некоторые типы операций идемпотентны изначально:
- Перезапись справочника — «контрагент с GUID X должен иметь название Y, ИНН Z». Сколько раз ни применить — результат один.
- Установка статуса — «заказ N перевести в статус «оплачен». Сколько раз ни применить — статус останется «оплачен».
- Удаление по идентификатору — первый раз удалит, второй увидит, что уже удалён, ничего не сделает.
Для таких сообщений отдельная защита не нужна — сам тип операции это решает. Но это нужно осознанно проверять, а не предполагать.
Опасный класс — операции инкремента и создания
Самый опасный класс операций — те, что не идемпотентны от природы:
- «Списать N единиц со склада» — повтор удвоит списание.
- «Создать платёж на сумму X» — повтор создаст второй платёж.
- «Начислить премию N сотруднику» — повтор удвоит начисление.
Для всех таких операций защита от дубликатов обязательна. Обмен с неидемпотентными операциями без явной защиты — бомба замедленного действия, которая рано или поздно сработает.
Что дальше
- Гарантия доставки — формальный контракт, из которого вытекает требование идемпотентности.
- Жизненный цикл сообщения — где в цикле возникают повторы.
- Структура сообщения обмена — где живёт идентификатор сообщения.