Как избежать бесконечного перекидывания сообщения в очереди при падении воркера
Что проверяют
Понимаете ли вы, что падение воркера и исключение в коде это разные ситуации. Исключение можно поймать и посчитать попытку, а процесс, убитый OOM-киллером, ничего посчитать не успеет, и брокер честно вернёт сообщение в очередь. Следом обычно спрашивают, как отличить «ядовитое» сообщение от временного сбоя.
Как отвечать
Начните с того, что подтверждение идёт после обработки (at-least-once), а значит, повторная доставка неизбежна и счётчик попыток нужно хранить вне воркера:
- RabbitMQ: quorum-очереди считают доставки сами, передают заголовок
x-delivery-countи после лимита отправляют сообщение в dead-letter exchange. В RabbitMQ 4.x лимит по умолчанию равен 20, его стоит снизить политикойdelivery-limit; - SQS: redrive policy с
maxReceiveCountпереносит сообщение в DLQ после N получений; - Kafka: в классических консьюмер-группах подтверждения по отдельному сообщению нет, есть коммит смещения. Ядовитую запись после нескольких попыток перекладывают в retry-топик или DLT и коммитят смещение, иначе консьюмер будет перечитывать её вечно;
- Celery:
acks_late=Trueвместе сtask_reject_on_worker_lost=Trueвозвращает задачу в очередь при гибели процесса, и документация прямо предупреждает, что так можно получить бесконечный цикл. Нужныmax_retries,retry_backoffи свой счётчик, например в Redis по id сообщения.
Добавьте экспоненциальную задержку между попытками, идемпотентный обработчик (повтор не должен списать деньги дважды) и алерт на рост DLQ.
Где спотыкаются
Предлагают try/except с тремя попытками внутри воркера. При SIGKILL или нехватке памяти этот код не выполнится, а счётчик в памяти процесса умрёт вместе с ним.
Делают nack с requeue=True без задержки. Сообщение мгновенно возвращается в очередь и крутится по кругу, забивая CPU и логи.
Считают DLQ решением. Если туда никто не смотрит и нет инструмента переотправки после фикса, это просто кладбище потерянных заказов.