Фоновые задачи в Django: Celery, RQ или без брокера
Отправка письма в обработчике запроса тормозит ответ на секунды. Разбираем, чем выносить работу в фон: Celery, RQ, django-q2 и когда хватит обычного управляющего процесса.

Пользователь жмёт «Зарегистрироваться», view отправляет письмо через SMTP, и ответ приходит через 1,5–3 секунды — ровно столько, сколько занимает разговор с почтовым сервером. Если SMTP лёг, запрос висит до таймаута и падает с 500. Классическая ситуация, где работу пора выносить из цикла запрос-ответ. Дальше — какие инструменты для этого берут на практике в проектах на Django 5.x, чем каждый платит и как не выбрать комбайн там, где хватило бы отвёртки.
Почему нельзя оставить это в view
Django обрабатывает запрос синхронно в рамках одного WSGI-воркера (gunicorn, uWSGI). Пока view ждёт ответа от SMTP, платёжного шлюза или генерации PDF, воркер занят и не обслуживает других. Десяток медленных запросов — и пул воркеров исчерпан, сайт отвечает 502.
Задача фонового обработчика — принять описание работы, вернуть управление view немедленно и выполнить работу отдельным процессом. Три вопроса, которые определяют выбор: нужен ли вам брокер сообщений, нужны ли периодические задачи по расписанию и готова ли команда обслуживать ещё один сервис.
Большинство проектов подключают Celery не потому, что он нужен, а потому что «так принято». Прежде чем ставить брокер, посчитайте: сколько задач в минуту у вас реально есть и переживёт ли пользователь задержку в пару секунд без фона вообще.
Четыре подхода и их цена
Celery + Redis или RabbitMQ
Стандарт де-факто для крупных проектов. Отдельный процесс-воркер забирает задачи из брокера (Redis или RabbitMQ), выполняет их, при падении повторяет. Есть расписание через celery beat, приоритеты очередей, роутинг задач по воркерам, отслеживание результата.
Цена: вы держите брокер (ещё один сервис в проде), процесс воркера и, для расписания, отдельный процесс beat. На маленьком проекте это три лишние движущиеся части, каждая из которых может упасть молча.
RQ (Redis Queue)
Проще Celery по устройству: очереди живут только в Redis, воркер — один процесс rq worker. Нет своего протокола сериализации со своими сюрпризами, всё прозрачно. Расписание — через отдельный пакет rq-scheduler. Django-интеграция — django-rq.
Цена: меньше возможностей роутинга и приоритетов, привязка к Redis (RabbitMQ не поддерживается). Для типичного «отправь письмо, сгенерируй отчёт» этого хватает с запасом.
django-q2
Форк заброшенного django-q, живой на момент написания. Ставится как приложение Django, брокером может быть Redis, но умеет работать и через саму базу данных как ORM-брокер — тогда отдельный сервис не нужен вовсе, только один management-процесс qcluster. Расписание встроено, настраивается через админку.
Цена: производительность ORM-брокера ниже, чем у Redis, — на тысячах задач в минуту база станет узким местом. Проект поддерживается силами небольшого сообщества, что стоит учитывать в долгую.
Без брокера вообще
Иногда фон не нужен. Если задача — просто «не блокировать ответ письмом», а трафик небольшой, хватает встроенных механизмов:
- Асинхронная view — Django поддерживает
async defво view с версии 3.1, но для реального параллелизма нужен ASGI-сервер (uvicorn, daphne) и async-совместимый код до самого низа. Синхронный ORM-запрос внутри async-view выполняется в пуле потоков и параллелизма не даёт. - Отдельный поток через
threading— годится для разовой отправки письма, но поток живёт внутри веб-процесса: при перезапуске gunicorn задача теряется, повторов нет, счётчик задач никто не ведёт. Для чего-то важнее уведомления так делать нельзя. - Cron + management-команда — если работа периодическая и не срочная (ночная выгрузка, чистка старых записей), обычный cron, вызывающий
python manage.py your_command, надёжнее любого брокера и не требует ничего держать в памяти.
Сравнение
| Критерий | Celery | RQ | django-q2 | cron + команда |
|---|---|---|---|---|
| Нужен брокер | Redis / RabbitMQ | Redis | Redis или БД | нет |
| Расписание | celery beat | rq-scheduler | встроено | сам cron |
| Повторы при сбое | да, гибко | да | да | нет |
| Лишних процессов в проде | 2–3 | 1 | 1 | 0 |
| Порог входа | высокий | низкий | низкий | минимальный |
Как это выглядит в коде
Для примера — задача отправки письма на Celery 5.x с Redis. Файл myproject/celery.py:
import os
from celery import Celery
# Указываем модуль настроек Django до создания приложения
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
app = Celery("myproject")
# Забираем настройки из settings.py по префиксу CELERY_
app.config_from_object("django.conf:settings", namespace="CELERY")
# Ищем tasks.py во всех приложениях проекта
app.autodiscover_tasks()
В settings.py — адрес брокера:
# Redis на локальной машине, база 0
CELERY_BROKER_URL = "redis://localhost:6379/0"
CELERY_RESULT_BACKEND = "redis://localhost:6379/0"
Задача в accounts/tasks.py:
from celery import shared_task
from django.core.mail import send_mail
@shared_task(bind=True, max_retries=3, default_retry_delay=60)
def send_welcome_email(self, user_email):
try:
send_mail(
subject="Добро пожаловать",
message="Спасибо за регистрацию.",
from_email=None, # возьмётся DEFAULT_FROM_EMAIL
recipient_list=[user_email],
)
except Exception as exc:
# Повтор через 60 секунд, максимум 3 раза
raise self.retry(exc=exc)
Вызов из view — методом .delay(), который кладёт задачу в очередь и сразу возвращает управление:
from .tasks import send_welcome_email
def register(request):
# ... создание пользователя ...
send_welcome_email.delay(user.email)
return redirect("registration_done")
Воркер запускается отдельной командой: celery -A myproject worker -l info. Точные флаги и опции сверяйте с документацией вашей версии Celery — набор параметров между мажорными релизами менялся.
На что напарываются в проде
- Передача объектов модели в задачу. Не передавайте
userцеликом — передавайтеuser.idи заново загружайте объект внутри задачи. Сериализованный объект устареет к моменту выполнения, а на несериализуемом упадёт с ошибкой. - Задача выполняется до коммита транзакции. Если view обёрнута в
transaction.atomic(), а вы вызвали.delay()внутри неё, воркер может забрать задачу и не найти запись — она ещё не закоммичена. Ставьте вызов черезtransaction.on_commit(lambda: send_welcome_email.delay(user.id)). - Воркер молча умер. Задачи копятся в брокере, пользователи не получают писем, а мониторинга нет. Настройте хотя бы алерт на длину очереди и на живость процесса воркера — иначе узнаете о проблеме из жалоб.
- Расписание запустилось дважды. Если celery beat поднят на двух серверах — периодическая задача сработает дважды. Beat должен быть в единственном экземпляре.
Когда что выбирать
- Работа периодическая, не срочная, повторы не нужны — cron + management-команда. Ничего не держите в памяти зря.
- Нужен фон, но проект небольшой и Redis уже есть — RQ или django-q2. Один процесс, минимум настройки.
- Не хотите поднимать даже Redis, задач немного — django-q2 с ORM-брокером. Помните про потолок по нагрузке.
- Тысячи задач в минуту, приоритеты, роутинг по очередям, несколько типов воркеров — Celery. Здесь его сложность оправдана.
Что делать дальше
Выбрав инструмент, первым делом настройте видимость: длину очереди и живость воркера выведите в мониторинг. Для Celery посмотрите Flower как дашборд, для RQ — rq-dashboard. Затем прогоните сценарий отказа: остановите воркер и проверьте, что задачи не теряются, а копятся и выполняются после перезапуска. Инвалидация результатов и идемпотентность задач (что будет, если задача выполнится дважды) — отдельная большая тема, но заложить on_commit и передачу id вместо объектов стоит сразу.
Задачи: математика, физика, химия: Разбор задачи по шагам с проверкой и похожей задачей
Спросить за 10 ₽Источники: Celery — First steps with Django, Django — Asynchronous support
Частые вопросы
А если у меня не Redis, а Memcached — Celery заработает?
Вызвал .delay(), а задача не выполняется — где искать?
Можно обойтись async def во view вместо брокера?
Задача упала на середине — данные останутся в полусостоянии?
django-q2 с брокером в базе — насколько это надёжно для боевого проекта?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.