Каковы плюсы и минусы отправки подтверждения в момент получения сообщения или после его обработки?
Что проверяют
Понимаете ли вы, что момент подтверждения определяет гарантию доставки. Следом почти всегда спрашивают: «А что будет, если воркер упадёт посередине?» и как вы защищаетесь от повторной обработки.
Как отвечать
Свяжите два варианта с семантикой доставки:
- ack при получении: это at-most-once. Повторов нет, пропускная способность выше, но если процесс упал во время обработки, сообщение потеряно. Годится для метрик и логов, где потеря события некритична;
- ack после обработки: это at-least-once. Потерь нет, но возможны дубли: побочный эффект уже случился (запись в базу, письмо), а ack не дошёл. Значит, обработчик обязан быть идемпотентным.
Покажите, как это выглядит в Python-стеке:
- RabbitMQ (
pika,aio-pika):auto_ack=Trueвbasic_consumeподтверждает при доставке, ручнойbasic_ackвызывают после обработки. Число неподтверждённых сообщений на потребителя ограничиваетbasic_qos(prefetch_count=...); - Kafka (
confluent-kafka,aiokafka): по умолчаниюenable.auto.commit=true, и смещение коммитится по таймеру, независимо от того, закончили вы обработку или нет. Для at-least-once автокоммит отключают и коммитят после обработки; - Celery: по умолчанию задача подтверждается прямо перед выполнением.
acks_late=Trueпереносит ack на момент после завершения, аtask_reject_on_worker_lostвозвращает задачу в очередь при гибели воркера.
Exactly-once в общем случае не бывает, его собирают из at-least-once и идемпотентности: ключ операции плюс таблица обработанных сообщений или уникальный индекс.
Где спотыкаются
Считают, что ack после обработки даёт «ровно один раз». Он даёт «хотя бы один», а дубли становятся вашей заботой.
Забывают про «ядовитые» сообщения. Сообщение, на котором обработчик всегда падает, при позднем ack возвращается в очередь бесконечно. Нужны лимит попыток и очередь недоставленных (DLQ).
Ставят большой prefetch_count вместе с поздним ack. При падении воркера в очередь вернутся сотни сообщений разом. А у Celery с брокером Redis длинная задача упирается в visibility_timeout: не подтверждённая за это время, она уйдёт другому воркеру.