Почему Celery теряет задачи, когда падает воркер
Ситуация: воркер Celery убивает OOM-killer или перезапускает деплой, и часть задач просто исчезает. В логах пусто, в очереди тоже. Разберёмся, почему так происходит.
Задача подтверждается до выполнения
По умолчанию воркер отправляет брокеру подтверждение (ack) сразу, как только забрал задачу, ещё до запуска. Если процесс умрёт посередине, брокер уже считает задачу выполненной.
Лечится настройкой task_acks_late: подтверждение уходит после завершения задачи. Вместе с ней стоит включить task_reject_on_worker_lost, иначе задача, чей процесс убили сигналом, всё равно будет подтверждена.
Prefetch забирает лишнее
Воркер заранее берёт из очереди несколько задач: worker_prefetch_multiplier, умноженный на число процессов. Для длинных задач это плохо — пока один процесс занят, остальные задачи висят в его локальном буфере. Для долгих задач ставлю множитель 1.
Visibility timeout у Redis
С Redis в роли брокера есть ещё одна ловушка. Неподтверждённая задача через visibility_timeout (по умолчанию час) снова становится видна, и её берёт другой воркер. Если задача честно работает дольше часа, она запустится второй раз. Таймаут должен быть больше самой долгой задачи.
app.conf.update(
task_acks_late=True,
task_reject_on_worker_lost=True,
worker_prefetch_multiplier=1,
broker_transport_options={"visibility_timeout": 3600},
)
Обратная сторона
С поздним подтверждением задача может выполниться дважды: например, если воркер упал уже после побочного эффекта, но до ack. Поэтому задачи нужно делать идемпотентными: проверять, не отправлено ли уже письмо, использовать INSERT … ON CONFLICT DO NOTHING, хранить ключ операции. «Хотя бы один раз» с идемпотентностью почти всегда лучше, чем «не больше одного раза» без гарантий.