Django с RabbitMQ, Celery и PostgreSQL: разработка и асинхронные задачи

Курс о построении веб-приложений на Django с PostgreSQL и асинхронной обработкой задач через Celery и RabbitMQ. Разберём архитектуру, настройку окружения, выполнение фоновых задач, надёжность и развёртывание для production.

1. Архитектура проекта: роли Django, PostgreSQL, Celery и RabbitMQ

Архитектура проекта: роли Django, PostgreSQL, Celery и RabbitMQ

Эта статья открывает курс и задаёт общую картину: какие компоненты участвуют в типичном Django-проекте с асинхронными задачами, за что каждый отвечает, и как они взаимодействуют.

Мы будем рассматривать связку:

  • Django — веб-приложение (HTTP API, админка, бизнес-логика).
  • PostgreSQL — основная реляционная база данных.
  • Celery — фреймворк для выполнения фоновых задач.
  • RabbitMQ — брокер сообщений, через который задачи попадают к воркерам.
  • Официальные источники:

  • Документация Django
  • Документация Celery
  • Документация RabbitMQ
  • Документация PostgreSQL
  • Зачем вообще нужна такая архитектура

    В веб-приложении есть операции двух типов:

  • Синхронные — должны завершиться быстро в рамках HTTP-запроса (например, показать страницу, создать запись, вернуть JSON).
  • Длительные или нестабильные — могут занимать секунды/минуты, зависят от внешних сервисов или выполняются массово (например, отправка email, генерация отчёта, обработка изображений, интеграции).
  • Если «длительные» операции выполнять прямо в обработчике HTTP-запроса, появляются проблемы:

  • пользователь ждёт слишком долго;
  • серверные воркеры (gunicorn/uwsgi) заняты и не обслуживают другие запросы;
  • любой временный сбой внешнего сервиса превращается в ошибку запроса;
  • становится сложнее масштабировать систему.
  • Выход — выносить такие действия в фоновые задачи: HTTP-запрос быстро фиксирует намерение и отдаёт ответ, а тяжёлая работа происходит отдельно.

    Роль каждого компонента

    Django

    Django — центральная точка, где живёт бизнес-логика и HTTP-интерфейс.

    Типичные обязанности Django в этой архитектуре:

  • принимать HTTP-запросы и возвращать ответы;
  • валидировать входные данные;
  • работать с моделями и транзакциями;
  • инициировать фоновые задачи (например, вызвать send_email.delay(...));
  • хранить настройки, правила доступа, админку.
  • Важно: Django не должен выполнять тяжёлую работу в запросе, если это можно вынести в Celery.

    PostgreSQL

    PostgreSQLисточник правды для бизнес-данных.

    Что обычно хранится в PostgreSQL:

  • доменные сущности (пользователи, заказы, платежи, файлы, статусы);
  • состояние процессов (например, «отчёт в обработке», «письмо отправлено», «экспорт завершён»);
  • связи между объектами и история изменений.
  • Чего PostgreSQL обычно не делает в этой связке:

  • не является очередью задач (очередь обеспечивает RabbitMQ);
  • не выполняет фоновые задачи (задачи выполняют Celery-воркеры).
  • RabbitMQ

    RabbitMQ — брокер сообщений. Его роль — надёжно принять сообщение о задаче и доставить его воркеру.

    В связке с Celery RabbitMQ обычно отвечает за:

  • хранение очередей сообщений;
  • маршрутизацию задач по очередям;
  • доставку сообщений одному из воркеров;
  • повторную доставку, если воркер умер до подтверждения выполнения.
  • Термины минимально:

  • Очередь — место, куда «кладутся» сообщения с задачами.
  • Сообщение — описание задачи и её аргументы.
  • Брокер — сервер, который принимает/хранит/выдаёт сообщения.
  • Celery поддерживает разные брокеры, но RabbitMQ часто выбирают за зрелость и предсказуемую модель очередей.

    Celery

    Celery — система фоновых задач: она описывает, как запускать задачи, как их ретраить, как планировать, как распределять по очередям.

    В Celery есть два ключевых элемента:

  • Producer (клиент) — часть, которая отправляет задачи в брокер (в Django-коде это обычно вызов .delay() или .apply_async()).
  • Worker (воркер) — процесс, который получает сообщения из RabbitMQ и выполняет Python-код задачи.
  • Что часто делает Celery:

  • запуск задач вне HTTP-запроса;
  • повторные попытки при сбоях (ретраи);
  • разделение задач по очередям (например, emails, reports, low_priority);
  • запуск периодических задач (в паре с Celery Beat).
  • Ссылка на концепции: Celery — Calling Tasks

    Как компоненты взаимодействуют

    Синхронный путь (обычный HTTP-запрос)

    Пример: пользователь открывает страницу профиля.

  • Django получает запрос.
  • Django читает данные из PostgreSQL.
  • Django формирует ответ.
  • RabbitMQ и Celery в этом пути могут не участвовать вообще.

    Асинхронный путь (запрос + фоновая задача)

    Пример: пользователь нажал «Сформировать отчёт», который делается 2–5 минут.

  • Django получает запрос.
  • Django создаёт запись в PostgreSQL, например Report(id=..., status="pending").
  • Django отправляет задачу в Celery (по факту — публикует сообщение в RabbitMQ).
  • RabbitMQ помещает сообщение в очередь.
  • Celery-воркер забирает сообщение и запускает код задачи.
  • Задача читает и пишет данные в PostgreSQL, обновляя статус, например status="done".
  • Пользователь позже запрашивает результат: Django читает итог из PostgreSQL и отдаёт.
  • !Диаграмма показывает два потока: быстрый HTTP-ответ и отдельное выполнение фоновой задачи

    Что считается «хорошим разделением ответственности»

    Практическое правило: Django управляет процессом, Celery выполняет работу, PostgreSQL хранит состояние, RabbitMQ доставляет команды на выполнение.

    Признаки здоровой архитектуры:

  • HTTP-обработчик делает минимум: валидация, транзакция, постановка задачи, быстрый ответ.
  • В задаче Celery нет зависимости от HTTP-объектов (например, нельзя передавать request).
  • В аргументах задачи передаются простые типы или идентификаторы (например, user_id, report_id).
  • Факт выполнения фиксируется в PostgreSQL через статусы и результаты.
  • Надёжность: что может пойти не так и как проектировать правильно

    Дублирование задач и идемпотентность

    В распределённых системах возможна ситуация «задача выполнится дважды»:

  • воркер выполнил задачу, но подтверждение брокеру не дошло;
  • задача была отправлена повторно по ретраю;
  • вы сами случайно поставили две одинаковые задачи.
  • Поэтому важное требование к задачам — идемпотентность: повторный запуск не должен ломать данные.

    Как этого добиваются на практике:

  • работать от идентификатора сущности (order_id) и проверять текущий статус в БД;
  • использовать уникальные ограничения в PostgreSQL там, где это уместно;
  • хранить «факт обработки» (например, timestamp или статус) и не выполнять действие повторно.
  • Транзакции Django и момент постановки задачи

    Типичная ошибка — поставить задачу в очередь, а затем транзакция с данными откатится. В итоге воркер стартует, а нужных данных в БД нет.

    Базовая идея правильного решения:

  • сначала надёжно сохранить данные в PostgreSQL;
  • затем ставить задачу.
  • На практике для Django часто используют постановку задачи после успешного коммита транзакции (мы разберём это в следующих статьях, когда дойдём до реализации).

    Ретраи и «постоянные ошибки»

    Celery умеет делать ретраи, но ретраи полезны только для временных проблем:

  • сеть;
  • временная недоступность внешнего API;
  • кратковременная перегрузка.
  • Если ошибка постоянная (например, неверные данные), бесконечные ретраи только забьют очередь. Нужны:

  • ограничения на количество попыток;
  • фиксация ошибки в PostgreSQL (статус failed и причина);
  • инструменты наблюдения (логи, метрики).
  • Масштабирование: как растёт система

    Главное преимущество разнесения ролей — независимое масштабирование.

  • Django масштабируется по HTTP-нагрузке (больше веб-процессов/инстансов).
  • Celery-воркеры масштабируются по числу и тяжести задач (больше воркеров, отдельные очереди).
  • RabbitMQ масштабируется под поток сообщений и требования надёжности.
  • PostgreSQL масштабируется отдельно (индексы, ресурсы, репликация, пул соединений).
  • Полезная идея на будущее: разные очереди под разные профили задач.

  • high_priority — критично быстро (уведомления, биллинг).
  • default — обычные фоновые операции.
  • cpu_heavy — CPU-нагрузка (отдельные воркеры).
  • Минимальная структура проекта (концептуально)

    На уровне репозитория обычно выделяют:

  • Django-проект и приложения (models/views/serializers);
  • модуль с Celery-конфигурацией;
  • модуль tasks.py в приложениях, где живут задачи;
  • инфраструктурные файлы (например, docker compose) для PostgreSQL и RabbitMQ.
  • С точки зрения запуска процессов в окружении это обычно выглядит так:

  • один или несколько процессов веб-сервера для Django;
  • один или несколько процессов Celery worker;
  • отдельный сервис RabbitMQ;
  • отдельный сервис PostgreSQL.
  • Итог

  • Django принимает запросы, управляет бизнес-процессом и ставит задачи.
  • PostgreSQL хранит все бизнес-данные и состояние выполнения.
  • RabbitMQ принимает и доставляет сообщения с задачами.
  • Celery исполняет задачи воркерами, управляет ретраями и очередями.
  • В следующих материалах курса мы перейдём от архитектуры к практике: соберём окружение, настроим Django + PostgreSQL, подключим Celery к RabbitMQ и реализуем первую задачу так, чтобы она была надёжной и наблюдаемой.

    2. Настройка окружения и конфигурация Django + PostgreSQL

    Настройка окружения и конфигурация Django + PostgreSQL

    В прошлой статье мы разобрали роли компонентов: Django управляет бизнес-логикой и HTTP, а PostgreSQL хранит состояние и данные. В этой статье соберём минимально рабочее окружение, где Django подключается к PostgreSQL, выполняет миграции и готов к дальнейшему подключению Celery и RabbitMQ.

    !Упрощённая схема: Django подключается к PostgreSQL по SQL, база запущена отдельным сервисом

    Что вы получите в конце

  • Запущенный PostgreSQL (через Docker Compose).
  • Django-проект, который подключается к этой базе.
  • Выполненные миграции и проверка подключения.
  • Предварительные требования

  • Установленный Python 3.11+ (желательно) и pip.
  • Установленный Docker и Docker Compose.
  • Полезные ссылки:

  • Документация Django
  • Документация PostgreSQL
  • Документация Docker
  • Документация Docker Compose
  • Создаём проект Django

    Вариант с виртуальным окружением

    Виртуальное окружение нужно, чтобы зависимости проекта устанавливались изолированно и не конфликтовали с другими проектами.

  • Создайте папку проекта и виртуальное окружение
  • Активируйте окружение
  • Установите Django и драйвер PostgreSQL
  • В Django для работы с PostgreSQL нужен Python-драйвер. Современный вариант: psycopg.

    Ссылка на драйвер:

  • Документация psycopg
  • Создайте проект
  • Пояснение: config это пакет с настройками, а точка в конце создаёт проект в текущей папке (без лишней вложенности).

    Запускаем PostgreSQL через Docker Compose

    Почему так удобно:

  • не нужно ставить PostgreSQL в систему;
  • проще повторить окружение на другой машине;
  • позже мы так же подключим RabbitMQ.
  • Создайте файл docker-compose.yml в корне проекта:

    Запустите базу:

    Проверка статуса:

    Ключевые понятия:

  • Контейнер это изолированный процесс с приложением (здесь PostgreSQL).
  • Volume это постоянное хранилище, чтобы данные не пропадали при пересоздании контейнера.
  • Настраиваем подключение Django к PostgreSQL

    Какие параметры нужны

    Для подключения Django к PostgreSQL обычно нужны:

  • имя базы данных;
  • пользователь;
  • пароль;
  • хост;
  • порт.
  • В нашем случае, когда PostgreSQL запущен через Compose и порт проброшен на localhost:

  • host: 127.0.0.1
  • port: 5432
  • Конфигурация DATABASES в Django

    Откройте config/settings.py и найдите переменную DATABASES. Замените на настройку PostgreSQL.

    Важно: в реальных проектах секреты не держат в коде. Даже в разработке полезно привыкать к переменным окружения.

    Пример настройки через переменные окружения (без сторонних библиотек):

    Пояснение:

  • ENGINE говорит Django, какой бэкенд базы использовать.
  • os.getenv берёт значение из окружения, а второй аргумент это значение по умолчанию.
  • Ссылка на раздел настроек базы:

  • Django: DATABASES
  • Как задать переменные окружения

    Вы можете оставить значения по умолчанию (как в примере выше). Но полезно знать, как задавать их явно.

  • macOS или Linux
  • Windows PowerShell
  • Примечание: позже, когда мы будем контейнеризировать Django, эти же переменные удобно передавать через Compose.

    Применяем миграции и проверяем подключение

    Миграции это механизм Django, который создаёт и изменяет таблицы в базе данных на основе моделей.

    Выполните команды:

    Если подключение настроено верно, Django создаст служебные таблицы (например, для пользователей, сессий и админки).

    Запустите сервер разработки:

    Откройте в браузере http://127.0.0.1:8000/ и убедитесь, что стартовая страница Django отображается.

    Чтобы проверить работу админки:

  • Создайте суперпользователя
  • Откройте http://127.0.0.1:8000/admin/ и войдите.
  • Типичные проблемы и быстрые решения

    Ошибка: не удаётся подключиться к 127.0.0.1:5432

    Проверьте:

  • контейнер PostgreSQL запущен: docker compose ps
  • порт проброшен и не занят другим процессом
  • в DATABASES указан правильный HOST и PORT
  • Ошибка: нет модуля psycopg или драйвера PostgreSQL

    Проверьте, что вы установили зависимости именно в активированное виртуальное окружение:

    Ошибка аутентификации пользователя

    Сверьте значения:

  • POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD в docker-compose.yml
  • параметры NAME, USER, PASSWORD в настройках Django
  • Если вы меняли креды после первого запуска, мог сохраниться старый volume с прежними настройками. Для полного сброса в разработке:

    Практические рекомендации под будущие Celery и RabbitMQ

  • Держите настройки подключения в переменных окружения: это упростит добавление RabbitMQ и Celery без переписывания кода.
  • Не используйте SQLite для проектов, где важны конкурентные обновления и фоновые задачи: PostgreSQL даёт более предсказуемое поведение.
  • Планируйте, что Celery-воркеры будут обращаться к той же базе: схема миграций и настройки доступа должны быть едиными.
  • Итог

    Вы подняли PostgreSQL в Docker, настроили Django на подключение через DATABASES, выполнили миграции и убедились, что проект работает. В следующих материалах эта база станет источником правды для процессов, которые будут запускаться асинхронно через Celery и доставляться через RabbitMQ.

    3. Celery в Django: задачи, очереди, ретраи и планировщик

    Celery в Django: задачи, очереди, ретраи и планировщик

    В предыдущих статьях мы:

  • разобрали роли Django, PostgreSQL, RabbitMQ и Celery в архитектуре;
  • подняли PostgreSQL и подключили к нему Django.
  • Теперь добавим выполнение фоновых задач: настроим Celery в Django, подключим брокер RabbitMQ, научимся отправлять задачи в очереди, корректно ретраить ошибки и запускать периодические задачи через планировщик.

    !Общая схема выполнения фоновых и периодических задач

    Что такое Celery в контексте Django

    Celery — это библиотека и набор процессов, которые позволяют выполнять Python-функции вне HTTP-запроса.

    В нашей связке:

  • Django создаёт задачу и отправляет сообщение в брокер.
  • RabbitMQ принимает и хранит сообщение в очереди.
  • Celery worker забирает сообщение и выполняет код.
  • PostgreSQL хранит бизнес-данные и состояние процесса.
  • Официальная документация:

  • Документация Celery
  • Django: transaction.on_commit
  • Документация RabbitMQ
  • Подготовка окружения: RabbitMQ и зависимости

    Добавляем RabbitMQ в Docker Compose

    Если у вас уже есть docker-compose.yml с PostgreSQL, добавьте сервис RabbitMQ:

    Запуск:

    Панель управления RabbitMQ будет доступна по адресу http://127.0.0.1:15672/.

  • логин: guest
  • пароль: guest
  • Устанавливаем Celery

    В активированном виртуальном окружении:

    Важно: Celery использует транспорт AMQP, который поддерживает RabbitMQ “из коробки”.

    Интеграция Celery с Django

    Наша цель: чтобы Celery автоматически подхватывал настройки Django и находил задачи в приложениях.

    Создаём config/celery.py

    Рядом с config/settings.py создайте файл config/celery.py:

    Что здесь происходит:

  • DJANGO_SETTINGS_MODULE говорит Celery, где лежат настройки Django.
  • Celery("config") создаёт объект приложения Celery.
  • namespace="CELERY" означает: Celery будет брать настройки из переменных вида CELERY_... в settings.py.
  • autodiscover_tasks() ищет tasks.py во всех Django-приложениях.
  • Подключаем Celery при импорте проекта

    Откройте config/__init__.py (создайте, если нет) и добавьте:

    Так Celery-приложение будет подниматься корректно при старте воркера.

    Настройки Celery в settings.py

    Добавьте в config/settings.py:

    Ключевые параметры:

  • CELERY_BROKER_URL — адрес RabbitMQ.
  • CELERY_TASK_DEFAULT_QUEUE — очередь по умолчанию.
  • CELERY_TASK_TIME_LIMIT — жёсткий лимит времени выполнения задачи в секундах.
  • CELERY_TASK_SOFT_TIME_LIMIT — мягкий лимит: задача получает исключение и может завершиться аккуратно.
  • CELERY_TASK_ACKS_LATE — подтверждать задачу брокеру после выполнения, а не до.
  • CELERY_WORKER_PREFETCH_MULTIPLIER = 1 — воркер не забирает пачку задач заранее, что часто полезно для “тяжёлых” задач.
  • Первая задача: как писать и как вызывать

    Создаём приложение и tasks.py

    Создайте Django-приложение, например core:

    Добавьте core в INSTALLED_APPS.

    Создайте core/tasks.py:

    Почему @shared_task удобно в Django:

  • задача не привязана жёстко к конкретному объекту app;
  • autodiscover_tasks() найдёт её автоматически.
  • Вызов задачи из Django-кода

    Пример из view (или сервиса), где мы ставим задачу в очередь:

    Важно понимать: delay() не выполняет задачу сейчас. Он сериализует аргументы, создаёт сообщение и публикует его в RabbitMQ.

    Запуск воркера

    В отдельном терминале (при активированном окружении):

  • -A config означает: “используй Celery-приложение из Django-проекта config”.
  • -l info включает информативные логи.
  • Надёжность: транзакции Django и постановка задач

    Одна из самых частых ошибок — отправить задачу в очередь до коммита транзакции.

    Сценарий проблемы:

  • Django создаёт запись в БД внутри транзакции.
  • Код ставит задачу в Celery.
  • Транзакция откатывается.
  • Воркер стартует задачу и не находит нужных данных.
  • Решение: ставить задачу после успешного коммита.

    Пример:

    Смысл transaction.on_commit(...): переданная функция вызовется только если транзакция завершится успешно.

    Очереди и маршрутизация задач

    Очереди нужны, чтобы разделять разные типы нагрузки.

    Типичный пример:

  • default — обычные задачи
  • emails — отправка писем
  • cpu — тяжёлые вычисления
  • Простая маршрутизация через CELERY_TASK_ROUTES

    Добавьте в settings.py:

    Если задача не указана в маршрутах — она попадёт в CELERY_TASK_DEFAULT_QUEUE.

    Запуск воркера на конкретной очереди

    Чтобы воркер слушал только очередь emails:

    На практике часто запускают несколько воркеров:

  • один обслуживает emails
  • другой обслуживает default
  • третий обслуживает cpu
  • Так вы получаете управляемое масштабирование.

    Ретраи: как правильно повторять задачи

    Ретраи нужны для временных ошибок:

  • сетевые сбои
  • временная недоступность внешнего API
  • кратковременные блокировки
  • Если ошибка постоянная (например, неверные входные данные), ретраи только создадут очередь из “мёртвых” задач.

    Ретрай вручную через self.retry

    Пояснения:

  • bind=True даёт доступ к self (контексту задачи).
  • max_retries=5 ограничивает число попыток.
  • countdown=10 задаёт задержку перед повтором в секундах.
  • Автоматические ретраи через autoretry_for

    Смысл настроек:

  • autoretry_for — какие исключения считать временными.
  • retry_kwargs — параметры ретраев (например, число попыток).
  • retry_backoff=True — увеличивать задержку между попытками.
  • Подтверждение выполнения и идемпотентность

    Что означает acks_late

    Когда CELERY_TASK_ACKS_LATE = True, задача подтверждается брокеру только после успешного выполнения.

    Это уменьшает риск потери задачи при падении воркера, но повышает шанс, что задача выполнится повторно (например, воркер сделал работу, но умер до подтверждения).

    Почему задачи должны быть идемпотентными

    Идемпотентность означает: повторный запуск задачи не должен ломать состояние.

    Практические приёмы:

  • передавать в задачу не “данные целиком”, а id сущности
  • проверять статус в PostgreSQL перед выполнением
  • использовать уникальные ограничения в БД там, где это уместно
  • Пример правильного интерфейса:

    Планировщик: периодические задачи через Celery Beat

    Периодические задачи — это задачи, которые запускаются по расписанию:

  • чистка временных данных
  • ежедневные отчёты
  • синхронизация со сторонним сервисом
  • Celery решает это через процесс beat, который по расписанию публикует задачи в брокер.

    Пример расписания в settings.py

    И задача:

    Запуск beat

    В отдельном терминале:

    Важно: beat не выполняет задачи. Он только ставит их в очередь. Выполняют задачи воркеры.

    Наблюдаемость: как понимать, что происходит

    Минимальный набор:

  • логи воркера (-l info или -l warning)
  • панель RabbitMQ Management для просмотра очередей
  • Для удобного веб-интерфейса очередей и задач часто используют Flower:

  • Документация Flower
  • Итог

    Вы настроили Celery в Django и получили базовый набор практик:

  • как описывать задачи и вызывать их через .delay()
  • как подключить RabbitMQ как брокер
  • как разводить нагрузку по очередям и запускать воркеры на нужных очередях
  • как делать ретраи для временных ошибок и ограничивать число попыток
  • как запускать периодические задачи через Celery Beat
  • почему важны transaction.on_commit, acks_late и идемпотентность
  • Дальше по курсу логично перейти к хранению статусов выполнения в PostgreSQL, результатам задач, а также к “боевым” настройкам: таймаутам, дедлеттер-очередям и обработке ошибок так, чтобы система оставалась наблюдаемой и предсказуемой.

    4. RabbitMQ на практике: обменники, routing, DLQ и мониторинг

    RabbitMQ на практике: обменники, routing, DLQ и мониторинг

    В предыдущих статьях курса мы настроили Django + PostgreSQL и подключили Celery к RabbitMQ как к брокеру сообщений. На этом этапе система уже работает, но в реальном проекте быстро возникают вопросы:

  • Почему часть задач должна идти в отдельные очереди и как это правильно настроить?
  • Как понять, куда именно попало сообщение и почему оно не обрабатывается?
  • Что делать с “ядовитыми” сообщениями, которые постоянно падают?
  • Как мониторить RabbitMQ, чтобы не узнавать о проблемах от пользователей?
  • Эта статья даёт практическое понимание RabbitMQ: обменники, маршрутизация, dead-letter (DLQ) и базовый мониторинг.

    Официальные источники:

  • RabbitMQ Documentation
  • RabbitMQ Management Plugin
  • Celery Documentation
  • Kombu Documentation
  • !Общая схема маршрутизации сообщений и попадания в DLQ

    Минимальная анатомия RabbitMQ

    Чтобы уверенно настраивать Celery и диагностировать проблемы, нужно различать базовые сущности.

  • Сообщение — данные, которые нужно доставить потребителю (в нашем случае это описание Celery-задачи и её аргументы).
  • Producer — тот, кто публикует сообщение (обычно Django-код, который вызывает .delay() или .apply_async()).
  • Consumer — тот, кто читает сообщения (Celery worker).
  • Очередь (queue) — буфер, где сообщения ждут обработки.
  • Обменник (exchange) — точка входа публикации: producer отправляет сообщение в exchange, а уже exchange решает, в какие очереди оно попадёт.
  • Привязка (binding) — правило, которое связывает exchange и очередь.
  • Routing key — строка в сообщении, по которой exchange может маршрутизировать сообщение.
  • Ключевая мысль: сообщение публикуют не “в очередь”, а “в обменник”, а дальше вступают правила маршрутизации.

    Типы обменников и как выбрать

    RabbitMQ поддерживает несколько типов exchange. На практике чаще всего вам нужен direct или topic.

    | Тип exchange | Как маршрутизирует | Когда использовать | |---|---|---| | direct | Точное совпадение routing key | Простое разнесение задач по очередям: emails, reports, default | | topic | Шаблоны по routing key (например, emails.*) | Когда нужна иерархия и гибкость, много видов задач | | fanout | Всем привязанным очередям | Broadcast-события, когда все должны получить копию | | headers | По заголовкам сообщения | Редко, специфичные случаи |

    Если вы только начинаете: используйте direct и отдельные очереди под классы задач. Если в проекте появляется сложная матрица маршрутов: переходите на topic.

    Как Celery использует RabbitMQ по умолчанию

    В предыдущей статье мы настроили Celery и начали отправлять задачи через RabbitMQ. Важно понимать, что Celery строит топологию RabbitMQ (exchange/queue/binding) автоматически.

    Типичный минимум:

  • есть очередь по умолчанию, например default
  • задачи без специальных правил маршрутизации попадают туда
  • В Django-коде вы делаете:

    Дальше Celery:

  • сериализует задачу в сообщение
  • публикует сообщение в брокер
  • воркер читает сообщение из очереди и выполняет задачу
  • Но как только вам нужно:

  • отделить emails от тяжёлых reports
  • иметь отдельный воркер на “критичные” задачи
  • изолировать нестабильные интеграции
  • вам нужна явная маршрутизация.

    Routing на практике: очереди, ключи и воркеры

    Бизнес-идея очередей

    Чаще всего очереди в проекте делят по профилю нагрузки:

  • default — обычные задачи
  • emails — всё, что связано с отправкой писем
  • cpu — тяжёлые CPU-задачи
  • integrations — нестабильные внешние API
  • Тогда вы можете запускать разные воркеры и управлять ресурсами.

    Маршрутизация в Celery через CELERY_TASK_ROUTES

    В предыдущей статье мы уже использовали CELERY_TASK_ROUTES. Это самый простой способ:

    После этого запускайте воркеры так, чтобы они слушали “свои” очереди:

    Когда этого недостаточно

    CELERY_TASK_ROUTES отвечает на вопрос “в какую очередь класть задачу”. Но RabbitMQ умеет больше:

  • routing key
  • разные exchange
  • dead-letter exchange
  • TTL
  • Для этого Celery использует Kombu-структуры Exchange и Queue.

    Явная топология: exchange, routing key и объявления очередей

    Ниже пример практичной схемы:

  • один topic-exchange tasks
  • routing key вида emails.send, reports.generate
  • очереди подписываются на шаблоны
  • Что это даёт:

  • вы видите маршруты как часть архитектуры
  • можно легко расширять группы задач
  • проще анализировать сообщения в RabbitMQ (по ключам)
  • DLQ: что это и зачем нужно

    DLQ (dead-letter queue) — “кладбище” сообщений, которые не были успешно обработаны.

    Важно: в RabbitMQ чаще говорят не “DLQ как функция”, а используют связку:

  • DLX (dead-letter exchange) — обменник, куда RabbitMQ перенаправляет “проблемные” сообщения
  • DLQ — очередь, привязанная к DLX, где эти сообщения накапливаются
  • Когда сообщение попадает в DLQ

    RabbitMQ может отправить сообщение в DLX в нескольких распространённых случаях:

  • Consumer отклонил сообщение
  • У сообщения истёк TTL
  • Переполнение очереди (например, из-за ограничений длины)
  • В контексте Celery самый частый сценарий — отклонение (reject) или “неуспешная” обработка, когда вы явно говорите брокеру: это сообщение обработать не смогли, не возвращай в эту же очередь бесконечно.

    Почему DLQ полезна именно в Celery

    У вас есть два принципиально разных класса падений:

  • временные ошибки: сеть, таймауты, кратковременная недоступность API
  • постоянные ошибки: некорректные данные, логическая ошибка, несовместимый формат
  • Временные ошибки логично ретраить. Постоянные ошибки нужно:

  • остановить, чтобы не забивать очередь
  • зафиксировать для расследования
  • DLQ — один из механизмов, который помогает “убрать в сторону” проблемные сообщения.

    DLQ в RabbitMQ через аргументы очереди

    Чтобы включить dead-letter, очередь объявляют со специальными аргументами:

  • x-dead-letter-exchange — имя DLX
  • x-dead-letter-routing-key — routing key, с которым сообщение попадёт в DLX (не всегда обязательно)
  • Пример: все сообщения из default при dead-letter будут уходить в dlx, а затем в очередь dead.

    Что важно понимать:

  • DLQ не “лечит” ошибки автоматически
  • DLQ делает ошибки управляемыми: вы можете смотреть, сколько их, какие они, и принимать решения
  • TTL и “отложенная повторная попытка” через DLX

    TTL (time-to-live) — время жизни сообщения. Если TTL истёк до обработки, RabbitMQ считает сообщение “просроченным”. Часто такие сообщения отправляют в DLX.

    Практический паттерн:

  • задача не должна выполняться прямо сейчас
  • вы хотите отложить её повтор на секунд
  • вы не хотите, чтобы воркер держал сообщение “у себя” и ждал
  • Тогда делают “retry-очередь”:

  • у неё стоит TTL
  • по истечении TTL сообщение dead-letter’ится обратно в рабочую очередь
  • Пример концепции на уровне очередей:

  • retry_30s имеет TTL 30 секунд
  • retry_30s настроена на DLX обратно в tasks / default
  • Пояснения:

  • x-message-ttl задаётся в миллисекундах
  • сообщение, попав в retry_30s, “полежит” 30 секунд
  • после чего уйдёт в default
  • Важно: этот паттерн полезен, но требует дисциплины в коде. Если вы автоматически будете перекидывать любую ошибку в retry-очередь, можно получить бесконечный цикл. Поэтому всегда ограничивайте число попыток и фиксируйте итоговый статус в PostgreSQL (мы к этому вернёмся дальше по курсу).

    Что смотреть в RabbitMQ Management

    У вас уже подключён образ rabbitmq:3-management, а значит доступен веб-интерфейс. Он нужен не “для красоты”, а для ответов на диагностические вопросы.

    Откройте интерфейс по адресу http://127.0.0.1:15672/ и проверьте:

  • Queues
  • Exchanges
  • Connections и Channels
  • Очереди: базовые метрики, которые решают 80% проблем

    В списке очередей обычно важнее всего:

  • Ready — сколько сообщений ждут обработки
  • Unacked — сколько сообщений выдано consumer’ам, но ещё не подтверждено
  • Consumers — сколько потребителей реально читает очередь
  • скорость поступления и обработки сообщений (rates)
  • Типовые интерпретации:

  • Ready растёт, Consumers = 0 — воркеры не запущены или не слушают эту очередь
  • Unacked растёт и не падает — воркеры “подвисли”, задачи слишком долгие, либо проблемы с подтверждениями
  • Ready растёт быстрее, чем обработка — не хватает воркеров или задача стала существенно тяжелее
  • Exchanges и bindings: куда “подписана” очередь

    Если сообщение “не приходит”, часто причина в маршрутизации:

  • exchange не тот
  • routing key не совпадает
  • binding отсутствует
  • В UI можно открыть exchange и увидеть, какие есть bindings (куда он отправляет сообщения).

    Базовая диагностика через rabbitmqctl

    Если вы работаете в окружении, где UI недоступен, полезно знать CLI-команды.

    Если RabbitMQ запущен в Docker, команды выполняются внутри контейнера:

    Другие практичные команды:

    Практические рекомендации для связки Django + Celery + RabbitMQ

  • Разносите очереди по профилю задач: это дешевле и надёжнее, чем пытаться “одним воркером” обслужить всё.
  • Следите за Unacked: при acks_late это индикатор зависших или слишком долгих задач.
  • Делайте задачи идемпотентными: при сбоях доставки и падениях воркеров возможны повторы.
  • Используйте DLQ для управляемого фейла: особенно для постоянных ошибок и “ядовитых” сообщений.
  • Фиксируйте состояние выполнения в PostgreSQL: RabbitMQ отвечает за доставку, но не за бизнес-состояние.
  • Итог

    RabbitMQ в связке с Celery — это не просто “брокер, который работает”. Это управляемая система маршрутизации и надёжной доставки.

    В этой статье вы разобрали:

  • чем отличаются queue и exchange, что такое binding и routing key
  • какие типы exchange бывают и что чаще использовать в Celery-проектах
  • как настраивать маршрутизацию задач по очередям и ключам
  • как устроены DLX/DLQ и зачем они нужны
  • как применять TTL и DLX для отложенных повторов
  • какие метрики смотреть в RabbitMQ Management и как быстро диагностировать проблемы
  • Дальше по курсу логично перейти к “приземлению” этого в бизнес-процесс: хранение статусов задач в PostgreSQL, обработка ошибок, ограничения ретраев и предсказуемое восстановление после сбоев.

    5. Production: масштабирование воркеров, безопасность и деплой

    Production: масштабирование воркеров, безопасность и деплой

    В предыдущих статьях курса вы собрали связку Django + PostgreSQL и добавили Celery + RabbitMQ с очередями, ретраями и DLQ. На production всё это начинает жить под реальной нагрузкой, а значит появляются три практических вопроса:

  • Как масштабировать обработку задач, не «сломав» систему?
  • Как закрыть типовые дыры безопасности (RabbitMQ, Django, секреты, сеть)?
  • Как деплоить так, чтобы обновления были предсказуемыми, а задачи не терялись?
  • Цель статьи — дать рабочий набор практик и минимальный шаблон деплоя.

    !Типовая схема деплоя Django + Celery + RabbitMQ + PostgreSQL

    Базовый принцип production: разделяем процессы

    В development вы могли запускать всё «как получится». В production важно помнить, что это разные процессы с разными профилями нагрузки.

  • Django web обслуживает HTTP и должен отвечать быстро.
  • Celery worker потребляет сообщения из RabbitMQ и выполняет задачи.
  • Celery beat публикует периодические задачи (сам ничего не выполняет).
  • RabbitMQ держит очереди и маршрутизирует сообщения.
  • PostgreSQL хранит состояние и бизнес-данные.
  • Практическое следствие: вы масштабируете Django и Celery независимо и настраиваете ресурсы под конкретный тип работы.

    Масштабирование Celery-воркеров

    Масштабирование «в ширину» и «внутрь процесса»

    Есть два рычага:

  • В ширину: больше экземпляров воркера (например, несколько контейнеров worker).
  • Внутрь процесса: параметр --concurrency (сколько задач параллельно выполняет один воркер).
  • Как выбрать:

  • Если задачи CPU-bound (тяжёлые вычисления), рост --concurrency быстро упрётся в CPU. Часто лучше больше воркеров, но с умеренной конкуррентностью.
  • Если задачи I/O-bound (сеть, ожидание API), можно поднимать конкуррентность выше или использовать другие пуллы (см. документацию).
  • Ссылка: Celery workers

    Пулы выполнения: prefork и альтернативы

    По умолчанию Celery чаще всего используют prefork (процессы). Он надёжный и предсказуемый.

  • prefork хорошо подходит для большинства задач.
  • Для I/O-задач иногда используют gevent или eventlet, но это требует дисциплины (кооперативность, совместимость библиотек).
  • Если вы не уверены — оставайтесь на prefork и масштабируйте «в ширину».

    Разделение очередей и воркеров как основной инструмент

    Из предыдущих статей:

  • вы разводите задачи по очередям (default, emails, cpu, integrations)
  • запускаете воркеры, слушающие только свою очередь
  • Это даёт контролируемое масштабирование:

  • тяжёлые задачи не «съедают» воркеры, нужные для быстрых
  • нестабильные интеграции не блокируют «здоровые» очереди
  • Пример команд запуска воркеров по очередям:

    Настройки, которые влияют на справедливость и повторы

    В статье про Celery мы включали:

  • CELERY_TASK_ACKS_LATE = True
  • CELERY_WORKER_PREFETCH_MULTIPLIER = 1
  • Почему это особенно важно в production:

  • ack late снижает риск потери задачи при падении воркера, но повышает вероятность повтора, поэтому задачи должны быть идемпотентными.
  • prefetch_multiplier=1 помогает «не забирать пачку» задач одним воркером, что делает систему более справедливой при разном времени выполнения.
  • Ссылка: Celery configuration

    Автомасштабирование конкуррентности

    Celery поддерживает --autoscale=max,min, которое изменяет число процессов внутри воркера.

    Пример:

    Практическая рекомендация:

  • автоскейл внутри воркера полезен, но не заменяет «масштабирование в ширину»
  • автоскейл сложнее прогнозировать, поэтому сначала стабилизируйте систему на фиксированных значениях
  • Beat в production: только один экземпляр

    Celery beat должен быть один, иначе периодические задачи будут публиковаться несколько раз.

    Подходы:

  • один контейнер/процесс beat
  • при оркестрации (например, Kubernetes) используйте лидер-элекцию или внешнее расписание
  • Если вам нужны периодические задачи с хранением расписания в БД, смотрят в сторону django-celery-beat, но это отдельная тема.

    PostgreSQL и соединения: скрытая причина деградации

    Частая production-проблема: воркеры и веб-процессы вместе создают слишком много соединений к PostgreSQL.

    Минимальные правила:

  • следите за лимитом соединений в PostgreSQL
  • планируйте суммарное число процессов: web_processes + sum(worker_concurrency)
  • если соединений много, используйте пулер соединений (часто PgBouncer)
  • Официально про управление пользователями и доступами: PostgreSQL: Database Roles

    Безопасность: что обязательно закрыть перед production

    Секреты и переменные окружения

    Правило: секреты не должны быть в репозитории.

    Держите в переменных окружения:

  • DJANGO_SECRET_KEY
  • DATABASE_URL или параметры PostgreSQL
  • CELERY_BROKER_URL
  • любые ключи сторонних API
  • И отдельно:

  • ротация секретов должна быть планируемой процедурой
  • доступ к секретам ограничен на уровне окружения и CI/CD
  • RabbitMQ: убираем гостевой доступ и закрываем Management

    Минимум для production RabbitMQ:

  • не использовать пользователя guest для приложений
  • создать отдельного пользователя под приложение
  • использовать vhost для изоляции
  • ограничить доступ к порту Management (15672) по сети
  • включить TLS, если трафик идёт по небезопасной сети
  • Официально:

  • RabbitMQ Access Control
  • RabbitMQ TLS Support
  • Django: базовый чеклист деплоя

    Перед деплоем обязательно пройдите официальный чеклист:

  • Django deployment checklist
  • Критично важные настройки:

  • DEBUG = False
  • ALLOWED_HOSTS настроен
  • HTTPS включён на уровне reverse proxy
  • CSRF_COOKIE_SECURE = True и SESSION_COOKIE_SECURE = True при HTTPS
  • SECURE_HSTS_SECONDS и связанные флаги (если вы точно готовы к HSTS)
  • Сеть: доступ «только кому нужно»

    Практичное правило: не публикуйте в интернет PostgreSQL и RabbitMQ.

  • PostgreSQL доступен только Django и Celery внутри приватной сети.
  • RabbitMQ доступен только Django и Celery внутри приватной сети.
  • Внешний доступ только к reverse proxy (например, Nginx) и, при необходимости, к ограниченному мониторингу.
  • Деплой: минимальный production-шаблон на Docker

    Ниже упрощённый шаблон, чтобы зафиксировать роль каждого процесса. В реальном production PostgreSQL и RabbitMQ часто выносят в managed-сервисы, но структура процессов от этого не меняется.

    Dockerfile для Django

    Gunicorn: Gunicorn documentation

    docker-compose для ролей web/worker/beat

    Что важно в этой схеме:

  • отдельные сервисы для разных очередей
  • отдельный сервис beat
  • зависимости указывают на порядок запуска, но не гарантируют готовность сервиса
  • Переменные окружения

    Пример .env:

    Миграции и статика как отдельные шаги

    В production миграции и сбор статики должны быть отдельным управляемым шагом, а не «магией при старте».

    Команды:

    Практика деплоя:

  • сначала выкатывается новая версия кода
  • затем выполняются миграции
  • затем перезапускаются web/worker/beat
  • Если миграции «ломающие», используйте приём expand and contract: сначала добавляете совместимые изменения схемы, потом обновляете код, потом удаляете старое.

    Обновления без потери задач

    Graceful shutdown воркеров

    При обновлении вы не хотите обрубить воркер посреди выполнения задачи.

    Базовые меры:

  • включать acks_late, чтобы задача не потерялась
  • давать воркеру завершиться корректно при остановке
  • не делать задачи слишком длинными без чекпоинтов и без фиксации статуса в PostgreSQL
  • Если задача длится минуты и часы, проектируйте её так, чтобы прогресс и итог фиксировались в БД, а повторный запуск был безопасен.

    Контроль «ядовитых» сообщений через DLQ

    Из статьи про RabbitMQ:

  • DLQ позволяет изолировать сообщения, которые не обрабатываются
  • мониторинг DLQ — один из самых быстрых индикаторов деградации качества данных или логики
  • В production договоритесь о политике:

  • кто смотрит DLQ
  • как сообщения переигрываются или исправляются
  • где фиксируется причина отказа (логи, таблица ошибок, алерты)
  • Наблюдаемость: что мониторить в первую очередь

    RabbitMQ

    В RabbitMQ Management чаще всего спасают три метрики на очередях:

  • Ready растёт, Consumers = 0 означает воркеры не запущены или слушают не ту очередь
  • Unacked растёт означает воркеры зависают, задачи очень долгие или проблемы с подтверждениями
  • скорость публикации выше скорости обработки означает не хватает воркеров или задача стала тяжелее
  • Документация Management: RabbitMQ Management Plugin

    Celery

    Минимально:

  • логи воркеров
  • понимание, какая очередь отстаёт
  • Для визуализации состояния часто используют Flower:

  • Flower documentation
  • Django

    Минимально:

  • ошибки 5xx
  • время ответа
  • насыщение по воркерам Gunicorn
  • Даже без сложного APM полезно иметь структурированные логи и корреляцию по request_id.

    Итог

    Для production-связки Django + Celery + RabbitMQ + PostgreSQL устойчивость достигается не одной настройкой, а набором дисциплин:

  • масштабируйте воркеры через очереди и отдельные группы воркеров
  • держите beat в единственном экземпляре
  • проектируйте задачи идемпотентными и фиксируйте состояние в PostgreSQL
  • не публикуйте RabbitMQ и PostgreSQL в интернет, используйте отдельные учётки, vhost и при необходимости TLS
  • деплойте предсказуемо: миграции и статика отдельными шагами, обновления с корректной остановкой воркеров
  • мониторьте очереди RabbitMQ и отставание обработки, а DLQ используйте как управляемый механизм отказа