Как передавать задачи в очередь для отложенного выполнения в Celery
Что проверяют
Понимаете ли вы путь задачи от вызова до воркера и что происходит с отложенными задачами внутри брокера. Базовые delay и apply_async знают все, поэтому следом спрашивают про ETA, повторную доставку и падение воркера посреди задачи.
Как отвечать
- отправка:
task.delay(a, b)это короткая записьtask.apply_async(args=(a, b)). Для отложенного запускаcountdown=60илиetaс таймзоной,expiresотменит задачу, если её не взяли вовремя,queue="emails"направит в отдельную очередь; - аргументы: передавайте id, а не ORM-объекты. Сериализатор по умолчанию JSON, модель через него не пройдёт, а если бы и прошла, воркер получил бы устаревший снимок. Свежие данные воркер достаёт из базы сам;
- транзакции: в Django задачу ставят через
transaction.on_commit, иначе воркер может прийти раньше коммита и не найти запись. В свежих версиях Celery для этого естьdelay_on_commit; - как устроены ETA: воркер забирает такую задачу из очереди сразу и держит в памяти до срока. С Redis задача, не подтверждённая за
visibility_timeout(по умолчанию час), возвращается в очередь и выполняется повторно. Поэтому отложенное на дни черезcountdownне делают: пишут в базу и запускают по расписанию через Celery Beat; - надёжность:
acks_late=Trueвместе с идемпотентной задачей,autoretry_forиretry_backoffдля временных ошибок,max_retries, чтобы не повторять вечно.
Воркер запускается командой celery -A proj worker -Q emails, а для периодических задач нужен отдельный процесс celery -A proj beat.
Где спотыкаются
Вызывают result.get() сразу после delay или, хуже, внутри другой задачи. Первое превращает асинхронность обратно в синхронный вызов, второе грозит взаимной блокировкой воркеров.
Ставят отложенные задачи на недели вперёд через eta и потом удивляются дублям и росту памяти воркеров.
Не делают задачи идемпотентными. При доставке at-least-once письмо или списание однажды выполнятся дважды.