Тормозит сайт из-за долгих операций? Научитесь выносить тяжёлую работу в фон с Celery, и ваше Django-приложение станет отзывчивым и быстрым.
Введение
Веб-сервис на Django запускает ресурсоёмкие операции, например, генерирует PDF‑отчёт о заказе или обрабатывает загруженную фотографию. Если такие задачи выполнять прямо во время HTTP-запроса, пользователю придётся скучать перед экраном в мучительном ожидании ответа. Это делает интерфейс медленным и раздражающим. Ускорить работу можно, если вынести тяжёлые операции в фоновый режим — особенно важно, когда приложение работает на VPS с ограниченными ресурсами. В этом помогает Celery. В материале разобрали настройку Celery: как вынести задачи из основного потока и ускорить Django-приложение.
Асинхронность на практике: когда без Celery уже никак
Когда Django-приложение сталкивается с длительной операцией, например, с генерацией отчёта, отправки сотен писем, обработкой изображений и так далее, эта операция блокирует основной поток выполнения. Весь сайт может тормозить, потому что сервер занят обработкой одной долгой задачи. Celery решает эту проблему через распределённую очередь задач: вы поручаете тяжёлую работу отдельному воркеру (фоновому процессу), а ваше веб-приложение сразу освобождается для обработки других запросов. Пользователи больше не раздражаются из-за подтормаживаний, сайт мгновенно отвечает на клики, а сложные задачи спокойно выполняются за кулисами.
Celery состоит из нескольких компонентов: брокер сообщений, очередь задач, воркер и, опционально, результатное хранилище. Когда вы отправляете задачу в Celery, она сериализуется и помещается в очередь через брокер. Отдельный процесс-воркер получает задачи из очереди и выполняет их, уже не влияя на основной веб-сервер. Результат выполнения можно сохранить, например, чтобы веб-приложение проверило статус задачи позже. В итоге ваш Django-сайт мгновенно возвращает ответ пользователю, а все тяжёлые расчёты происходят асинхронно.
Установка Celery и выбор брокера сообщений
Первым делом потребуется установить Celery и программу-брокер, через которую Celery будет передавать задачи. Брокер сообщений — это посредник между вашим Django-приложением и воркерами Celery. Он принимает задачи и распределяет их по очередям. Celery поддерживает разные брокеры, но для начала проще всего использовать Redis — он прост в настройке и может работать сразу и брокером, и хранилищем результатов.
Выполните команду установки через pip:
pip install celery redis
Затем нужно установить и запустить сервер Redis. В среде Linux это можно сделать командой sudo apt install redis-server (для Ubuntu/Debian) либо запустить Redis через Docker.
Убедитесь, что Redis работает (по умолчанию на порту 6379). Если вы предпочитаете RabbitMQ, установите и запустите его — принципиальной разницы на уровне кода Celery не будет, изменится лишь URL брокера.
Запуск Celery в проекте
После установки переходим к интеграции Celery в Django-проект. Предположим, ваш проект называется myproject. В каталоге с файлом настроек Django создайте новый файл celery.py:
# myproject/celery.py
import os
from celery import Celery
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
app = Celery('myproject')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()
Сначала задаётся переменная окружения DJANGO_SETTINGS_MODULE, чтобы Celery знал, где лежат настройки проекта. Потом создаётся экземпляр Celery с именем проекта — это просто ярлык, по которому его можно будет идентифицировать. Метод config_from_object подтягивает все переменные, начинающиеся с CELERY_, из settings.py. Удобно: не нужно плодить отдельные конфиги. А app.autodiscover_tasks() сам находит файлы tasks.py во всех приложениях из INSTALLED_APPS — никаких ручных импортов. Благодаря этому вам не придётся вручную регистрировать задачи, достаточно объявить их в приложениях.
Теперь откройте ваш myproject/settings.py. Добавьте туда минимальные настройки Celery — указание брокера и, опционально, хранилища результатов. Для Redis это выглядит так:
# myproject/settings.py (фрагмент)
CELERY_BROKER_URL = 'redis://localhost:6379/0'
CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'
CELERY_BROKER_URL указывает, как подключаться к брокеру сообщений. Здесь это локальный Redis (база 0). CELERY_RESULT_BACKEND задаёт хранилище результатов — используем тот же Redis. Если отслеживать результат задач вам не нужно, от бэкенда можно вовсе отказаться, чтобы сэкономить ресурсы, например, указать CELERY_RESULT_BACKEND = None. Но для начала полезно видеть результаты, поэтому оставим Redis.
Последний штрих конфигурации: в файл myproject/__init__.py добавьте строчку для импорта вашего приложения Celery при старте Django:
from .celery import app as celery_app
При запуске Django инициализируется Celery. Переменная celery_app будет доступна, и декораторы задач смогут её найти.
Создание задачи (task) в Django-приложении
Настало время написать первую задачу. Создайте в одном из ваших Django-приложений файл tasks.py или откройте существующий и объявите там функцию, которую нужно выполнять асинхронно. Например, реализуйте простую задачу отправки приветственного письма новому пользователю:
# someapp/tasks.py
from celery import shared_task
from django.core.mail import send_mail
@shared_task
def send_welcome_email(user_id):
# Импортируем модель внутри задачи, чтобы избежать проблем при инициализации
from django.contrib.auth import get_user_model
User = get_user_model()
try:
user = User.objects.get(id=user_id)
except User.DoesNotExist:
return "User not found"
# Формируем письмо
send_mail(
subject="Добро пожаловать!",
message="Спасибо за регистрацию, {0}!".format(user.username),
from_email="noreply@example.com",
recipient_list=[user.email],
fail_silently=True,
)
return "Sent welcome email to {}".format(user.email)
Здесь декоратор @shared_task регистрирует функцию как задачу Celery. Мы используем shared_task, чтобы Celery мог найти задачу без прямого импорта объекта app. Функция получает user_id, загружает пользователя из базы и отправляет ему e-mail с помощью встроенной Django-функции send_mail. В конце возвращаем строку с результатом, она будет сохранена в Redis (RESULT_BACKEND), и при необходимости её можно будет там посмотреть.
Задачи Celery должны при повторном выполнении давать тот же результат. Например, если задача отправки письма случайно выполнится дважды, пользователю придёт два письма. Это не критично, но в других случаях, например, списании денег, повтор опасен. Celery может выполнять задачу повторно при сбоях, поэтому старайтесь проектировать задачи так, чтобы повторное выполнение не ломало данные или предусмотреть проверку, выполнялась ли задача ранее.
Вызов задачи из Django и асинхронное выполнение
Предположим, после регистрации нового пользователя вы хотите отправить приветственное письмо. Вместо прямого вызова send_welcome_email() используйте метод Celery:
# someapp/views.py (фрагмент)
from someapp.tasks import send_welcome_email
def register_user(request):
# ... логика регистрации пользователя …
user = create_new_user(request.form)
# Запускаем задачу Celery на отправку письма
send_welcome_email.delay(user.id)
return render(request, "registration_success.html")
Метод .delay() вызывает задачу асинхронно через Celery. Этот вызов мгновенно вернёт управление, поместив задачу в очередь Redis. Воркер Celery подхватит задачу и начнёт выполнять её фоном, а код сразу отправит пользователю страницу с сообщением об успешной регистрации. Благодаря этому письмо отправится без задержки — страница регистрации загрузится быстро, а уведомление улетит ему на почту уже после. Вы также можете вызвать задачу с помощью apply_async(), что даёт больше контроля, например, указать очередь или время выполнения, но .delay() — удобный сокращённый вариант для простых случаев.
Запуск воркера Celery и проверка работы
Убедитесь, что брокер (Redis) уже запущен. Затем в директории проекта, где находится manage.py, выполните команду:
celery -A myproject worker --loglevel=INFO
Параметр -A myproject говорит Celery, где искать объект приложения. Мы назвали его по имени проекта, app = Celery('myproject'). После запуска вы увидите в терминале баннер Celery и множество служебной информации. Если всё настроено правильно, воркер подключится к брокеру и будет ждать задач.
Теперь попробуем на практике. Выполните Django-сервер разработки (python manage.py runserver) в одном окне терминала, а в другом запустите Celery-воркер. Когда вы вызовете действие, отправляющее задачу, например, зарегистрируете пользователя на сайте, вызывая send_welcome_email.delay, в логе воркера тут же появится сообщение о принятии задачи. Celery выдаст что-то вроде:
[INFO/MainProcess] Task someapp.tasks.send_welcome_email[абв123] received
[INFO/ForkPoolWorker] Task someapp.tasks.send_welcome_email[абв123] succeeded in 0.5s: 'Sent welcome email to user@example.com'
Это означает, задача выполнена успешно, метод вернул строку результата. Поздравляем, Django-приложение теперь выполняет задачи асинхронно!
Оптимизация и масштабирование Celery-задач
Фон уже разгрузили, теперь можно выжать из Celery максимум.
Самое простое — отделить воркеры от веб-приложения. Как если бы повара и официанты работали в разных залах: никто никому не мешает. На серверах AdminVPS легко поднять отдельную машину под задачи, чтобы фронтенд не зависал при всплеске нагрузки.
Дальше стоит отрегулировать количество процессов. Для тяжёлых вычислений — по числу ядер, не больше. А если задачи в основном ждут ответа от внешних сервисов, масштабируйте шире, например, через -P gevent.
Задачи тоже бывают разными. Одни выполняются быстро, другие тянут время. Если не разделить их по очередям, всё смешается: отчёт на 5 минут может задержать уведомление, которое идёт секунду. Настройте отдельную очередь для тяжёлых задач, задайте worker_prefetch_multiplier=1. Остальные пойдут в очередь по умолчанию.
Celery самостоятельно решает, сколько процессов держать. Укажите --autoscale=10,1 — один процесс стартует сразу, остальные подключаются по мере необходимости.
Чем дольше работает воркер, тем выше риск утечек памяти, поэтому перезапускайте его каждые 100–1000 задач. Параметр CELERY_WORKER_MAX_TASKS_PER_CHILD как раз для этого.
Результаты тоже можно отключить, если они не нужны. Без них Redis меньше забивается, всё работает быстрее.
Следить за этим удобно через Flower. Веб-интерфейс показывает, что происходит в очереди, какие задачи зависли, сколько работает воркеров. А для отладки пригодится режим CELERY_TASK_ALWAYS_EAGER: задачи выполняются сразу, как обычные функции. Главное не забыть выключить это в продакшене.
Заключение
Celery берёт на себя всю грязную работу, позволяя сайту летать даже под нагрузкой. Пользователи больше не застанут сервис врасплох тяжёлыми операциями: будь то рассылка тысячи писем или обработка видео. Всё выполняется фоном, пока фронтенд мгновенно отвечает на запросы. Конечно, настройка Celery требует времени и понимания, зато результат оправдывает усилия. Вы получаете более отзывчивое приложение, которое готово масштабироваться и справляться с серьёзными задачами.
Читайте в блоге:
- Зачем нужен перенос сайта на другой хостинг
- Оптимизация CSS и JavaScript: как ускорить загрузку сайта
- Оптимизация производительности сервера Ubuntu 24.04 LTS: что важно настроить