Отправка письма, генерация 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 и супервизора.