Backend

Очереди и вебхуки: как не потерять сообщения

А Александр Митряков30 мая 2026 г. 4 мин
О

Как только в системе появляется слово «уведомить», «синхронизировать» или «обработать в фоне», появляются очереди. А вместе с ними — целый класс проблем, которые не воспроизводятся на тестовом стенде и приходят в три часа ночи.

Общее правило, из которого мы исходим: доставка «ровно один раз» — это миф. На практике доступны две гарантии: «не более одного раза» (можно потерять) и «хотя бы один раз» (можно получить дубль). Почти всегда выбирают вторую и делают обработчик устойчивым к дублям. Дубль пережить можно, потерянную оплату — нет.

Отсюда первое требование: идемпотентность обработчика. Одно и то же событие, обработанное дважды, должно давать тот же результат, что и обработанное один раз. Простейшая реализация — таблица обработанных идентификаторов событий: перед работой проверяем, не видели ли уже, после работы записываем. Важно, чтобы запись факта обработки и сама работа были в одной транзакции, иначе остаётся окно, в котором можно упасть между ними.

Второе — ретраи с экспоненциальной задержкой. Внешний сервис не ответил — не долбим его в цикле, а откладываем: через секунду, через две, через четыре, через восемь. Без этого ваша система превращается в инструмент добивания уже упавшего сервиса, и он не поднимется, пока вы не перестанете.

К ретраям обязательно нужен потолок. Бесконечно повторяемая задача, которая не может выполниться в принципе — например, ссылается на удалённый объект, — будет крутиться вечно и займёт воркеры. Поэтому третье: dead-letter очередь. После N неудачных попыток сообщение уходит туда и ждёт человека.

Про dead-letter стоит сказать отдельно, потому что её обычно заводят и забывают. Очередь, в которую никто не смотрит, — это просто медленный способ терять данные. На неё нужен алерт: появилось сообщение — кто-то должен узнать. Мы обычно шлём в рабочий чат.

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

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

Мы всегда принимаем вебхук в две стадии. Первая: проверить подпись, положить сырое тело в хранилище, ответить 200. Вторая: обработать асинхронно. Соблазн обработать сразу в обработчике велик, но тогда любая ваша внутренняя ошибка превращается в неуспешную доставку у отправителя, а некоторые сервисы после нескольких неудач отключают вебхук совсем.

Сырое тело сохраняем всегда. Когда через месяц выяснится, что события разбирались неправильно, единственный способ восстановить данные — переразобрать оригиналы. Если их нет, данные потеряны навсегда.

Подпись проверяем до разбора. Эндпоинт вебхука открыт наружу, и туда прилетает всякое.

Из практики: почти все инциденты с очередями, которые мы разбирали, сводились к трём вещам. Обработчик не был идемпотентным, и дубль создал вторую запись. Не было потолка ретраев, и одна плохая задача забила всех воркеров. Или dead-letter была, но в неё никто не смотрел.

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

Понравилась статья?

У нас много таких историй — и ещё больше идей для вашего продукта.

Обсудить проект