Celery и Django: фоновые задачи без боли
Как подключить Celery к Django-проекту, вынести отправку писем и генерацию отчётов из веб-запроса и не собрать типовые грабли с брокером, сериализацией и запуском воркера.

Вы отправляете письмо прямо в обработчике формы, SMTP отвечает три секунды, и всё это время пользователь смотрит на крутящийся индикатор. Или Gunicorn убивает запрос по таймауту на генерации PDF. Задача — вынести всё, что длится дольше сотни-другой миллисекунд, за пределы веб-запроса. К концу этого гайда у вас будет рабочий Celery-воркер, который принимает задачи из Django и выполняет их в фоне. На настройку с нуля уходит около 30–40 минут.
Материал написан для Celery 5.x и Django 4.2 / 5.x. В качестве брокера берём Redis — с ним меньше возни, чем с RabbitMQ, а для старта его достаточно.
Что нужно иметь до начала
Проверьте это до первой команды, иначе споткнётесь на середине:
- работающий Django-проект (структура с
manage.pyи пакетом настроек, напримерmyproject/settings.py); - Python 3.8+ и активированное виртуальное окружение;
- установленный и запущенный Redis. Проверить:
redis-cli pingдолжен ответитьPONG. Если Redis нет — на Linux ставится через пакетный менеджер, на macOS через Homebrew, на Windows проще запустить в Docker; - терминал, в котором вы сможете держать открытым отдельное окно под воркер — он работает как самостоятельный процесс.
Шаг 1. Установка
Ставим Celery и клиент Redis:
pip install "celery[redis]"Квадратные скобки подтягивают зависимость redis вместе с Celery. Проверка: celery --version покажет версию 5.x.
Шаг 2. Файл celery.py рядом с настройками
Создайте файл myproject/celery.py — в том же пакете, где лежит settings.py. Замените myproject на реальное имя вашего пакета настроек.
import os
from celery import Celery
# Указываем Django, где лежат настройки, до создания приложения Celery
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()Проверка: файл создан, синтаксических ошибок нет. Запускать пока нечего.
Шаг 3. Подключаем Celery к __init__.py
Откройте myproject/__init__.py (тот же пакет) и добавьте:
from .celery import app as celery_app
__all__ = ("celery_app",)Это позволяет приложению Celery загрузиться при старте Django, и задачи с декоратором @shared_task будут зарегистрированы. Без этой строки автообнаружение задач тихо не сработает.
Шаг 4. Настройки брокера в settings.py
Добавьте в конец settings.py:
# Адрес брокера: сюда воркер ходит за задачами
CELERY_BROKER_URL = "redis://localhost:6379/0"
# Куда складывать результаты выполнения задач
CELERY_RESULT_BACKEND = "redis://localhost:6379/0"
# Разрешаем только JSON — безопаснее, чем pickle по умолчанию
CELERY_ACCEPT_CONTENT = ["json"]
CELERY_TASK_SERIALIZER = "json"
CELERY_RESULT_SERIALIZER = "json"
# Часовой пояс задач согласуем с проектом
CELERY_TIMEZONE = "Europe/Moscow"Число /0 в URL — номер базы Redis. Если Redis уже используется под кеш, возьмите другую базу, например /1, чтобы задачи и кеш не мешались.
Шаг 5. Первая задача
В любом приложении, например orders, создайте файл orders/tasks.py:
from celery import shared_task
from django.core.mail import send_mail
@shared_task
def send_order_confirmation(email, order_number):
"""Отправляет письмо-подтверждение заказа в фоне."""
send_mail(
subject=f"Заказ {order_number} принят",
message="Спасибо за заказ, мы уже собираем его.",
from_email="shop@example.com",
recipient_list=[email],
)
return f"Письмо отправлено на {email}"Декоратор @shared_task (а не @app.task) не привязывает задачу к конкретному экземпляру Celery — так задачу можно переиспользовать в разных приложениях. Проверка: файл на месте, импортируется без ошибок.
Шаг 6. Запуск воркера
В отдельном окне терминала, из корня проекта, при активном виртуальном окружении:
celery -A myproject worker -l infoФлаг -A myproject указывает на пакет с celery.py, -l info — уровень логов. В выводе вы должны увидеть строку вроде [tasks] со списком найденных задач, среди которых orders.tasks.send_order_confirmation, и в конце celery@hostname ready. Если задача в списке есть — обнаружение работает.
Воркер — это отдельный процесс. Он не перезапускается сам при изменении кода задач: правите tasks.py — гасите воркер по Ctrl+C и запускаете заново. Забытый перезапуск даёт самую частую иллюзию «мои правки не применяются».
Шаг 7. Вызов задачи из кода
В обработчике представления вызывайте задачу через .delay():
from orders.tasks import send_order_confirmation
def checkout_view(request):
# ... сохранили заказ ...
# Не send_order_confirmation(...), а .delay() — уходит в очередь
send_order_confirmation.delay("user@example.com", "A-1024")
return redirect("order_done")Метод .delay() — короткая форма для .apply_async(). Он не выполняет задачу, а кладёт сообщение в Redis и мгновенно возвращает управление. Проверка: обновите страницу оформления, в окне воркера появится строка Task orders.tasks.send_order_confirmation[...] received, а следом succeeded.
Если не получилось
Пять ошибок, которые видят почти все на старте:
consumer: Cannot connect to redis://localhost:6379/0— Redis не запущен или слушает другой адрес. Проверьтеredis-cli ping. В Docker localhost из контейнера — это не хост-машина.- Задача не появилась в списке
[tasks]при старте воркера — либо забыли строку импорта в__init__.py(шаг 3), либо приложение не добавлено вINSTALLED_APPS, либо файл называется неtasks.py. Received unregistered task of type '...'— задачу отправили, но воркер её не знает. Обычно воркер запущен из старой версии кода или из другого окружения. Перезапустите воркер.- Задача уходит в очередь, но
succeededне появляется — воркер не запущен или запущен с другим-A. Задачи копятся в Redis и выполнятся, когда воркер поднимется. Object of type ... is not JSON serializable— передаёте в задачу объект модели или datetime. Передавайте примитивы: вместо объекта заказа — егоid, а внутри задачи доставайте из базыOrder.objects.get(pk=order_id).
Что настроить дальше
Базовый конвейер работает. Следующие шаги, когда дойдёте до продакшена:
- Периодические задачи. Раз в час чистить корзины, ночью слать отчёт — это Celery Beat, отдельный процесс-планировщик. Настройка через
CELERY_BEAT_SCHEDULE, см. документацию. - Повторные попытки. Внешний API упал — задачу можно перезапустить через
self.retry(), объявив задачу сbind=True. - Демонизация. В проде воркер не запускают руками — его держит systemd или supervisor, чтобы он поднимался после перезагрузки.
- Мониторинг. Flower показывает очереди и статусы задач в веб-интерфейсе — полезно, когда задач становится много.
Передача моделей и результатов между задачами (chains, groups), выбор между Redis и RabbitMQ под большой нагрузкой — отдельные темы, здесь мы разобрали только базовый запуск.
Задачи: математика, физика, химия: Разбор задачи по шагам с проверкой и похожей задачей
Спросить за 10 ₽Источники: Celery — First steps with Django (официальная документация), Celery — User Guide: Tasks
Частые вопросы
Что делать, если у меня RabbitMQ, а не Redis?
Обязательно ли держать окно с воркером открытым?
Почему мои изменения в tasks.py не применяются?
Можно ли обойтись без Celery для простых фоновых задач?
Работает ли этот пример в Django 5.x, если он написан для 4.2?
Задача выполнилась, но результат мне нужен в представлении сразу. Как быть?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.