Перейти к содержанию

Идемпотентность

Идемпотентная операция — операция, повторное выполнение которой даёт тот же результат, что и единственное. Применительно к обработке сообщений: «обработать одно и то же сообщение N раз = обработать его один раз». Никаких дублирующих документов, повторных списаний, разъезжающихся остатков.

В модели at-least-once идемпотентность обработчика — не пожелание, а обязательное требование. Без неё обмен гарантированно сломается при первом же сбое между «приняли сообщение» и «отправили ACK».

Почему дубликаты возможны

DataHUB фиксирует подтверждение доставки после записи сообщения в локальный пул приёмника (см. Жизненный цикл сообщения). Сценарий, в котором возникает дубликат:

  1. Шина выдала сообщение с идентификатором X приёмнику.
  2. Приёмник записал его в свой локальный пул, транзакция закоммитилась.
  3. Приёмник пытается отправить шине ACK — в этот момент сеть оборвалась, либо процесс упал.
  4. Шина ACK не получила → считает сообщение недоставленным → через таймаут выдаёт его снова.
  5. Приёмник видит сообщение с идентификатором X второй раз — в его пуле такая запись уже есть.

Этот сценарий не редкий: он возникает при любых сетевых неприятностях, при перезагрузке приёмника в момент приёма, при ручной повторной выдаче администратором. Закладываться надо сразу.

Где нужна идемпотентность

Из-за двухфазной модели приёмника (приём в пул → отложенная обработка) защита от дубликатов нужна в двух разных местах.

На фазе приёма. Шина может повторно выдать сообщение, если ACK не пришёл. Приём должен корректно отрабатывать запись дубликата в локальный пул — повторная попытка вставки с тем же идентификатором распознаётся и игнорируется, не приводит ко второй копии в пуле.

На фазе прикладной обработки. Одно и то же сообщение из пула может быть обработано несколько раз — при ретрае из-за ненайденной ссылки, при сбое посередине, при ручном переоткате администратором. Обработчик должен распознавать «эта запись уже превратилась в документ» и не создавать второй.

Ключ к идемпотентности — стабильный идентификатор

Идемпотентность работает потому, что у сообщения есть уникальный идентификатор, не меняющийся между попытками доставки. Это одно и то же логическое сообщение, не «копия» — именно по этому идентификатору приёмник распознаёт повтор.

Идентификатор присваивается на стороне источника при формировании сообщения и сохраняется на всём пути — см. Жизненный цикл сообщения / Сквозной идентификатор.

Естественно идемпотентные операции

Не все обработки требуют отдельной защиты от дубликатов. Некоторые типы операций идемпотентны изначально:

  • Перезапись справочника — «контрагент с GUID X должен иметь название Y, ИНН Z». Сколько раз ни применить — результат один.
  • Установка статуса — «заказ N перевести в статус «оплачен». Сколько раз ни применить — статус останется «оплачен».
  • Удаление по идентификатору — первый раз удалит, второй увидит, что уже удалён, ничего не сделает.

Для таких сообщений отдельная защита не нужна — сам тип операции это решает. Но это нужно осознанно проверять, а не предполагать.

Опасный класс — операции инкремента и создания

Самый опасный класс операций — те, что не идемпотентны от природы:

  • «Списать N единиц со склада» — повтор удвоит списание.
  • «Создать платёж на сумму X» — повтор создаст второй платёж.
  • «Начислить премию N сотруднику» — повтор удвоит начисление.

Для всех таких операций защита от дубликатов обязательна. Обмен с неидемпотентными операциями без явной защиты — бомба замедленного действия, которая рано или поздно сработает.

Что дальше