Как реализовать периодическую задачу в Celery для запуска каждую ночь
Что проверяют
Знаете ли вы, что периодику в Celery запускает отдельный процесс celery beat, а не воркер, и понимаете ли, что происходит с расписанием в проде: часовой пояс, дубли, пропущенные запуски. Следом обычно спрашивают, что будет, если задача упала или beat запущен в двух экземплярах.
Как отвечать
Каркас ответа из четырёх частей:
- задача: обычная функция с
@app.task(или@shared_taskв Django). Beat ищет её по имени, а имя по умолчанию включает модуль:proj.tasks.nightly_report, а не простоnightly_report. Надёжнее задатьname=явно; - расписание:
app.conf.beat_scheduleс записью"schedule": crontab(hour=3, minute=0). Ровно полночь лучше не брать, туда же обычно сходятся бэкапы и другие кроны; - часовой пояс:
crontabсчитается вapp.conf.timezone, по умолчанию это UTC. Безtimezone = "Europe/Moscow"задача «на три ночи» уйдёт в шесть утра по Москве; - процессы: воркер и beat запускаются отдельно,
celery -A proj workerиcelery -A proj beat. Флаг-Bу воркера годится только для разработки.
Для Django стоит упомянуть django-celery-beat с DatabaseScheduler: расписание живёт в базе и меняется из админки без деплоя.
Дальше про надёжность. Beat только кладёт сообщение в брокер, выполнит его воркер, когда освободится. Поэтому ночная задача должна быть идемпотентной: повторный запуск за ту же дату не должен задваивать данные. Полезны options={"expires": ...}, чтобы не выполнять устаревший запуск после долгого простоя, и autoretry_for с retry_backoff для временных сбоев. Длинную задачу разумно ограничить time_limit.
Где спотыкаются
Запускают beat в каждом контейнере вместе с воркером. При трёх репликах ночной отчёт уходит трижды. Экземпляр beat должен быть ровно один, а если это не гарантировано, задачу защищают блокировкой в Redis или в базе.
Забывают про часовой пояс и потом удивляются, что задача срабатывает «не тогда». Сверять надо настройку timezone приложения, а не время на сервере.
Считают, что beat следит за выполнением. Он не знает, упала ли задача: мониторинг нужен отдельно, через Flower, логи или метрику «последний успешный запуск».