Когда нужно отправлять подтверждение (ack) в очередь после получения сообщения?
Что проверяют
Понимаете ли вы, что момент подтверждения задаёт семантику доставки: ack до обработки даёт «не больше одного раза», ack после неё даёт «хотя бы один раз» с дублями. Следом спрашивают, как сделать обработчик идемпотентным и что делать с сообщением, которое падает всегда.
Как отвечать
Базовое правило: ack уходит после того, как результат надёжно сохранён (транзакция в БД закоммичена, внешний сервис ответил успехом). Упадёт процесс раньше, брокер вернёт сообщение в очередь, и его обработает другой консьюмер.
Детали на примере RabbitMQ и pika:
auto_ack=True: сообщение считается доставленным в момент отправки консьюмеру и при падении теряется. Для важных данных только ручнойbasic_ack(delivery_tag);basic_nackиbasic_reject: сrequeue=Trueсообщение вернётся, сrequeue=Falseуйдёт в dead letter exchange, если он настроен;basic_qos(prefetch_count=N): сколько неподтверждённых сообщений консьюмер держит одновременно. Без лимита один воркер заберёт всю очередь;- тот же канал:
delivery_tagдействует в рамках канала, ack из другого канала закроет его с ошибкойPRECONDITION_FAILED.
Раз дубли неизбежны, обработчик делают идемпотентным: идентификатор сообщения в таблице обработанных, INSERT ... ON CONFLICT DO NOTHING, upsert вместо вставки.
В других системах смысл тот же. В Kafka подтверждение это коммит смещения партиции, поэтому enable.auto.commit выключают и коммитят после обработки. В Celery по умолчанию ack приходит до выполнения задачи, а подтверждение после включают через task_acks_late.
Где спотыкаются
На постоянную ошибку отвечают nack с requeue=True. Битое сообщение возвращается в очередь и крутится бесконечно. Нужны счётчик попыток, DLX или x-delivery-limit у quorum-очередей.
Считают, что ack после обработки даёт «ровно один раз». Он даёт «хотя бы один раз», а эффект «ровно один раз» достигается только идемпотентностью потребителя.
Держат сообщение неподтверждённым слишком долго. У RabbitMQ есть consumer_timeout, после которого канал закрывается, а в BlockingConnection долгая обработка в потоке соединения мешает heartbeat, и брокер рвёт соединение.