Как избежать дублирования выполнения задач и спама пользователя при повторной обработке сообщений из очереди
Что проверяют
Понимаете ли вы, что брокер доставляет сообщения «хотя бы один раз», а не «ровно один», и что защищаться от повторов должен сам обработчик. Следом обычно спрашивают: что будет, если воркер упал после отправки письма, но до подтверждения сообщения.
Как отвечать
Начните с причин повторов: воркер обработал задачу и умер до ack, консьюмер Kafka не закоммитил offset перед ребалансом, продюсер переотправил сообщение по таймауту. В Celery с acks_late=True задача вернётся в очередь при падении воркера, а на Redis-брокере ещё и по истечении visibility_timeout (по умолчанию час). Вывод: обработчик должен быть идемпотентным.
- ключ идемпотентности: берётся из бизнес-смысла, а не генерируется при каждой отправке. Например,
order_idплюс тип уведомления; - атомарная отметка: таблица обработанных ключей с уникальным индексом и
INSERT ... ON CONFLICT DO NOTHINGв той же транзакции, что и изменение данных. Вставка не прошла, значит, сообщение уже обработано; - внешние эффекты: письмо или пуш транзакцией не откатить. Помогает outbox: событие «отправить уведомление» пишется в базу вместе с данными, отдельный отправщик шлёт его и помечает отправленным;
- быстрый фильтр:
SET key 1 NX EX 86400в Redis дёшево отсекает дубли, но уникальный индекс не заменяет, ключ может истечь; - защита от спама: ограничивайте частоту на пользователя, например не больше одного уведомления одного типа за N минут.
Отдельно скажите, что «exactly once» в Kafka работает только внутри Kafka и на письмо во внешний сервис не распространяется.
Где спотыкаются
Делают проверку и запись двумя шагами: SELECT, потом INSERT. Два воркера одновременно видят «ещё не обработано» и оба шлют письмо. Нужна атомарная операция на уровне базы.
Помечают сообщение обработанным до побочного эффекта. Воркер падает между отметкой и отправкой, и уведомление теряется навсегда.
Берут ключом UUID сообщения, который продюсер генерирует заново при каждом ретрае. Дубль получает новый идентификатор и проходит все проверки.