Очередь не обещает exactly once
https://www.youtube-nocookie.com/embed/MwI4qp3ykAc
Автор: Артём Малышев (Positive Technologies) · PyCon Russia 2016
У фоновой задачи есть как минимум два неприятных исхода: она может потеряться или выполниться повторно. Переключение флага подтверждения меняет вероятность каждого исхода, но не создаёт гарантии exactly once.
Артём Малышев разбирает Celery и RQ — более простую Python-очередь задач, построенную вокруг Redis, — через их внутреннее устройство: как задача попадает в broker, когда считается принятой, где хранится во время выполнения и что происходит при падении worker. За одинаково удобным вызовом фоновой функции скрываются разные транспортные механизмы и разные окна отказа.
В финале доклада звучит главный практический ответ: задачу нужно по возможности делать идемпотентной. Если операция запустилась второй раз, она должна уметь определить, была ли уже выполнена, и безопасно завершиться без повторного эффекта. Broker помогает доставить сообщение, но не может решить эту часть бизнес-логики вместо приложения.
Кому смотреть: разработчикам систем, где повторная отправка письма неприятна, а повторное списание денег или повторное изменение состояния уже недопустимо.
Из этого можно взять в работу: для каждой критической фоновой задачи явно записать, что произойдёт, если она будет выполнена дважды.
⚠️ Доклад записан в 2016 году. Конкретные детали Celery, RQ, Redis и RabbitMQ нужно воспринимать как устройство версий того времени. Но модель отказов, которую объясняет автор, остаётся полезной.
RabbitMQ предоставляет специализированный протокол доставки: consumer подтверждает получение сообщения, а QoS ограничивает количество неподтверждённых сообщений, назначенных одному обработчику. Redis предлагает структуры данных, поверх которых библиотека очередей сама строит похожее поведение. От реализации этого промежуточного слоя зависит, можно ли потерять задачу между извлечением из основной очереди и сохранением состояния обработки.
Celery и RQ различаются и моделью исполнения. В докладе Celery представлен через worker pool, тогда как RQ использует более прямолинейную схему с отдельным fork-процессом для работы. Высокоуровневые возможности тоже требуют инфраструктуры: например, chord должен определить, что вся параллельная группа завершилась, и только после этого запустить callback. Для такой координации уже недостаточно просто положить функцию в очередь.
Настройка acks_late сдвигает подтверждение Celery-задачи ближе к завершению обработки. Это помогает переназначить работу после падения worker, но создаёт модель at least once: задача может прийти снова. Раннее подтверждение уменьшает вероятность повторного выполнения, зато после аварии подтверждённая, но незавершённая работа может быть потеряна.
Поэтому exactly once — не свойство, которое можно получить одной настройкой Celery. Оно требует протокола на уровне самой операции: идемпотентного ключа, проверки предыдущего результата или другого способа отличить повтор от новой команды.
Original post: https://territory.network/@engineers-tail/208/ochered-ne-obeshchaet-exactly-once