PythonDevOpsДеплой

Фоновые задачи в Python: Celery, RQ или свой воркер — что выбрать

5 мин чтения

Отправка письма, генерация PDF, обработка загруженного изображения, рассылка — такую работу нельзя делать прямо в обработчике HTTP-запроса. Пользователь ждёт ответа, веб-воркер занят и не обслуживает других, а долгая операция упирается в таймаут reverse proxy. Решение — фоновые задачи: приложение кладёт задание в очередь и сразу отвечает, а выполняет его отдельный процесс. Разберём, из чего это состоит и чем запускать.

Из чего состоит фоновая обработка

В любой реализации три части:

  • Producer — приложение, которое кладёт задачу в очередь (например, в обработчике запроса).
  • Брокер — хранилище очереди. Чаще всего Redis, для сложных сценариев — RabbitMQ.
  • Worker — отдельный процесс, который забирает задачи из очереди и выполняет.

Ключевое здесь — worker это отдельный долгоживущий процесс, не часть веб-сервера. Его нужно запускать и держать запущенным наравне с приложением (под супервизором — см. как держать процесс 24/7).

RQ — простой путь

Для большинства задач достаточно RQ: очередь поверх Redis с минимумом кода. Приложение кладёт задачу:

from redis import Redis
from rq import Queue

queue = Queue(connection=Redis())
queue.enqueue(send_email, user_id, subject)  # вернётся мгновенно

А воркер запускается отдельной командой и сам разбирает очередь:

rq worker

Понятная модель, легко отлаживать, легко понять, что происходит. Для пет-проектов и большинства продуктов этого хватает.

Celery — мощнее, но сложнее

Celery нужен, когда требуется больше: несколько очередей с приоритетами, маршрутизация задач, хранение результатов, расписание, гибкие ретраи. Цена — выше порог входа и больше движущихся частей.

from celery import Celery

app = Celery("tasks", broker="redis://localhost:6379/0")

@app.task
def send_email(user_id, subject):
    ...

Воркер запускается так:

celery -A tasks worker

Если очереди простые, Celery часто оказывается избыточным. Брать его стоит осознанно, под конкретные требования, а не по умолчанию.

Периодические задачи

Задачи по расписанию (ночной отчёт, очистка) делают одним из трёх способов:

  • Celery Beat — планировщик Celery, кладёт задачи в очередь по расписанию.
  • RQ Scheduler — аналог для RQ.
  • Системный cron, который дёргает management-команду или скрипт.

Важно не запускать периодическую задачу в нескольких экземплярах: два Beat-процесса поставят задачу дважды.

RQ или Celery: как выбрать

Простое правило — начинать с RQ и переходить на Celery, когда упираешься в его ограничения. RQ подходит, если задачи однотипные, брокер — Redis, а нужно «положить в очередь и выполнить». Celery оправдан, когда появляются несколько очередей с приоритетами, маршрутизация задач по разным воркерам, хранение и связывание результатов, сложное расписание или работа через RabbitMQ. Брать Celery «на вырост» под простые задачи — значит платить сложностью без отдачи.

За очередью важно наблюдать: у RQ для этого есть rq-dashboard, у Celery — Flower. Они показывают, что в очереди, что выполняется и что упало, — без такой панели ошибки в фоне легко не заметить.

Что важно в проде

  • Воркер — под супервизором. Упавший воркер должен подниматься сам, иначе очередь молча копится.
  • Идемпотентность. Очереди дают доставку «хотя бы один раз» (at-least-once): при сбое задача может выполниться повторно. Списание денег или отправка письма должны быть устроены так, чтобы повтор не навредил, — через проверку «уже сделано».
  • Ретраи с задержкой. Внешний сервис временно недоступен — задачу стоит повторить с нарастающей паузой, а не уронить.
  • Видимость. Нужно видеть упавшие задачи (failed registry в RQ, мониторинг в Celery), иначе ошибки теряются.
  • Сохранность очереди. Брокер должен переживать перезапуск: у Redis для этого включают persistence (RDB или AOF), иначе при рестарте все невыполненные задачи пропадут.

Частые грабли

  • Воркер не видит конфигурацию. Процесс запущен без тех же переменных окружения, что и приложение, — и падает на подключении к базе.
  • Долгая задача блокирует воркер. Один воркер обрабатывает задачи по очереди; для параллелизма поднимают несколько воркеров или повышают concurrency.
  • Потеря задач при падении брокера. Redis без persistence теряет очередь при перезапуске.
  • Двойное выполнение. Без идемпотентности повторно выполненная задача шлёт второе письмо или делает второе списание.
  • Раздувшаяся очередь. Воркер упал и не поднялся — задачи копятся, пользователи не получают результат, а заметно это становится не сразу.

Чеклист

  • Тяжёлые операции вынесены из обработчика запроса в фоновую задачу.
  • Выбран инструмент под задачу: RQ для простого, Celery — под расписание, приоритеты и результаты.
  • Воркер запущен отдельным процессом под супервизором с автоперезапуском.
  • Задачи идемпотентны, настроены ретраи с задержкой.
  • У брокера (Redis) включён persistence.
  • Есть способ видеть упавшие задачи.

Фоновая обработка добавляет к приложению ещё два постоянно живущих компонента — брокер и воркер, — которые тоже нужно где-то держать и обслуживать. На Hostim managed Redis (или Dragonfly) и Background Worker поднимаются рядом с приложением в одном проекте: воркер держится платформой как отдельный процесс, а очередь — на managed-брокере с бэкапами, без ручной настройки persistence и супервизора.

Читать дальше