Облачные триггеры обычно гарантируют доставку как минимум один раз, а не ровно один. Если функция повторно получает событие после таймаута или ошибки подтверждения, она может создать второй заказ, файл, платеж или уведомление.

Считайте повтор нормальным сценарием. Выделите стабильный event id или business key и зафиксируйте обработку атомарно до необратимого побочного действия.

Что сделать в первую очередь

  • Найдите дубли и сопоставьте их с исходным event id.
  • Проверьте retry policy, timeout и dead-letter настройки триггера.
  • Определите операции, которые нельзя безопасно выполнить дважды.
  • Сохраните фактический порядок попыток для одного события.

Почему возникает проблема

Симптом обычно появляется не из-за одной настройки. Сначала разделите путь данных на этапы и найдите место, где фактическое поведение расходится с ожидаемым.

  • Функция завершила работу, но подтверждение не дошло до платформы.
  • Timeout наступил после внешнего действия, но до сохранения статуса.
  • Каждая попытка генерирует новый внутренний идентификатор.
  • Параллельные экземпляры одновременно проверяют отсутствие результата.

Пошаговая диагностика

  • Сравните request id платформы и стабильный id исходного события.
  • Постройте timeline записи в базу и внешнего API-вызова.
  • Проверьте уникальные индексы и условные операции.
  • Смоделируйте ошибку сразу после побочного действия.
  • Проверьте, содержит ли ответ внешнего сервиса idempotency key.

Как исправить

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

  • Создайте inbox-таблицу с уникальным event id.
  • Резервируйте обработку атомарным insert или compare-and-set.
  • Передавайте стабильный idempotency key в поддерживаемые внешние API.
  • Сохраняйте результат, чтобы повтор мог вернуть его без нового действия.
  • Отправляйте окончательно неуспешные события в DLQ для управляемого разбора.

Как проверить результат

  • Десять повторов одного события создают один бизнес-результат.
  • Параллельная обработка не обходит уникальное ограничение.
  • Сбой после внешнего вызова восстанавливается без второго действия.

Как не допустить повторения

  • Добавляйте тесты повторной и параллельной доставки.
  • Мониторьте retry count, DLQ и долю deduplicated событий.
  • Храните бизнес-ключ отдельно от request id конкретной попытки.

Частые вопросы

Можно ли отключить retry?

Это уменьшит дубли, но повысит потери. Надежнее сделать обработку идемпотентной.

Достаточно ли проверить запись перед insert?

Нет при параллельной работе. Нужен уникальный индекс или атомарная условная операция.

Когда стоит обратиться за помощью

Если дубль затрагивает платежи или заказы, сначала остановите опасное побочное действие и сохраните входящие события. Затем можно безопасно восстановить очередь.

Итог

Повторная доставка — штатное свойство событийной системы. Я могу добавить idempotency, inbox, DLQ и восстановление функции без дублей данных.