Проектирование очередей задач: рабочие приёмы для продакшена
Одна общая очередь быстро превращает фоновые задачи в лотерею: тяжёлый экспорт блокирует письма, повторы создают дубли, а незаметный рост backlog приводит к сбоям. Разбираем, как проектировать очереди на RabbitMQ 4.x.

Когда экспорт отчёта на 20 минут попадает в одну очередь с отправкой письма на 200 миллисекунд, пользователь ждёт письмо вместе с отчётом. Если задача падает после внешнего API, повтор может создать второй заказ или списать деньги ещё раз. Ниже — практические приёмы проектирования очередей для RabbitMQ 4.x: как разделять потоки, ограничивать конкуренцию, безопасно повторять задачи и понимать, что система действительно перегружена.
Сначала разделите задачи по цене ошибки
Очередь нужна не только для того, чтобы выполнить работу «потом». Она отделяет запрос пользователя от операции, которая может быть медленной, нестабильной или временно недоступной. Но общая очередь без правил превращается в общий склад проблем.
Перед выбором очередей выпишите свойства каждой задачи:
- допустимую задержку: секунды, минуты или часы;
- стоимость повторного запуска;
- среднее и максимальное время выполнения;
- зависимости: база данных, файловое хранилище, внешний API;
- действие после окончательной ошибки;
- допустимую параллельность.
Задача отправки уведомления и пересчёта большого каталога могут использовать один брокер сообщений, но не должны конкурировать за одних и тех же работников. Брокер доставляет сообщения, а политика обработки должна жить в проекте: в коде задачи, настройках потребителей и мониторинге.
Главный вопрос при проектировании очереди — не «как положить задачу в брокер», а «что произойдёт, если задача выполнится дважды, не выполнится вообще или будет выполняться шесть часов».
Идемпотентность — свойство операции, при котором повторный запуск приводит к тому же бизнес-результату, что и один запуск. Для очередей это не академический термин: доставка и выполнение — разные события, поэтому обработчик должен переживать повтор.
Три схемы, которые подходят разным нагрузкам
| Схема | Когда использовать | Что выигрываете | Чем платите |
|---|---|---|---|
| Одна очередь для всех задач | Небольшой сервис, одинаковая длительность операций | Минимум конфигурации и простая диагностика | Долгие задачи блокируют быстрые, трудно выделить ресурсы |
| Очереди по классу работы | Есть быстрые, тяжёлые и внешние операции | Раздельное масштабирование и понятные лимиты | Нужно распределять работников и следить за несколькими backlog |
| Очереди по приоритету | Есть срочные задачи, но полностью разделить потоки нельзя | Быстрые операции получают ресурс раньше | Низкий приоритет может голодать при постоянном потоке срочных задач |
На практике чаще всего работает второй вариант. Например, можно выделить очереди notifications, payments, exports и webhooks. Названия здесь условные, но принцип важен: граница очереди должна совпадать с границей ответственности и масштабирования.
Как выбрать границу очереди
Разделяйте поток, если для него нужен другой лимит параллельности, другой таймаут, другой владелец или другое поведение при ошибке. Не создавайте очередь для каждого типа события только ради порядка: десятки почти пустых очередей усложнят наблюдаемость и выпуск изменений.
Рабочий критерий простой: если из-за роста одного вида задач вы готовы отдельно добавлять потребителей, менять лимит соединений или временно останавливать обработку, этому виду нужна собственная очередь.
Повторы: ограничивайте причину, а не только количество
Повторять стоит не каждую ошибку. Временный ответ внешнего сервиса, разрыв соединения или краткая недоступность базы могут исчезнуть. Ошибка в формате данных, отсутствие обязательного объекта или нарушение бизнес-правила повтором не исправятся.
- Разделите ошибки на временные и постоянные. Для временных задайте ограниченное число повторов и увеличивающуюся задержку между ними. Для постоянных сразу отправляйте сообщение в отдельное хранилище ошибок или очередь разбора.
- Добавьте случайную составляющую к задержке. Если тысячи задач повторяются в одну секунду после сбоя API, сервис получает вторую волну нагрузки. Случайное расхождение времени повторов уменьшает такой шип.
- Зафиксируйте предел времени. Четыре повтора за час и четыре повтора за четыре секунды — разные политики. Выбирайте предел по сроку актуальности операции, а не по красивому числу.
- Сохраняйте причину. В журнале нужны идентификатор задачи, тип ошибки, число попыток, время следующей попытки и ссылка на бизнес-объект. Без этого очередь ошибок превращается в кладбище сообщений.
Один и тот же механизм повторов нельзя одинаково применять к платежу, письму и пересчёту поискового индекса. Платёж требует идемпотентного ключа и проверки состояния операции, письмо — контроля дублей, индекс можно пересобрать из источника.
Подтверждение доставки не равно успешной обработке
В RabbitMQ сообщение может быть подтверждено потребителем после получения, но до завершения бизнес-операции. Если процесс завершится между этими событиями, сообщение исчезнет, а работа останется незавершённой. Если подтверждать только после обработки, при сбое возможна повторная доставка. Второй вариант безопаснее, но требует идемпотентного обработчика.
Для каждой задачи определите момент, после которого её можно считать выполненной. Для отправки webhook это может быть запись успешного ответа и сохранение идентификатора попытки. Для экспорта — закрытый файл в хранилище и запись его версии в базе. Подтверждайте сообщение после этой точки, а не сразу после чтения.
Не смешивайте транспортный и бизнес-статус
Статус сообщения в брокере отвечает на вопрос «забрал ли его потребитель». Статус операции в базе отвечает на другой вопрос: «достигнут ли нужный бизнес-результат». Если хранить только первый статус, невозможно отличить потерянную задачу от задачи, которая успешно завершилась, но не успела подтвердиться.
Для критичных операций полезно хранить журнал попыток: время старта, время окончания, результат, текст ошибки без секретов и версию обработчика. Это помогает разбирать повторную доставку и выпускать исправление без ручного поиска по логам.
Backlog — это не просто число сообщений
Очередь из 10 000 коротких задач может быть менее опасной, чем очередь из 100 тяжёлых. Поэтому одного размера backlog недостаточно. Наблюдайте минимум за четырьмя показателями:
- возраст самого старого сообщения;
- скорость поступления и скорость обработки;
- доля повторов и окончательных ошибок;
- время выполнения задачи и его верхние перцентили.
Если поступление стабильно быстрее обработки, очередь будет расти независимо от размера брокера. Увеличение числа потребителей помогает только пока хватает CPU, памяти, соединений с базой и лимитов внешнего сервиса. Иначе вы просто переносите очередь из RabbitMQ в базу данных или в API партнёра.
Для каждой очереди задайте действие при превышении возраста сообщения: поднять число работников, временно отключить низкоприоритетный поток, включить ограничение приёма или признать внешний сервис недоступным. Алерт только на количество сообщений обычно срабатывает слишком поздно.
Что ломается первым
Дубли после таймаута
Внешний сервис мог принять запрос, но не успеть вернуть ответ. Потребитель считает операцию неудачной и повторяет её. Защита — идемпотентный ключ на стороне операции, уникальное ограничение в базе или проверка уже сохраненного результата перед повторной отправкой.
Ядовитые сообщения
Сообщение с ошибочными данными падает при каждом запуске и занимает работников. После ограниченного числа попыток его нужно выводить в dead-letter queue — отдельную очередь для сообщений, которые не удалось обработать по штатной политике. Dead-letter queue, или DLQ, — место для изолированных сообщений, требующих анализа или ручного решения.
Голодание низкого приоритета
Приоритетные задачи могут постоянно занимать всех работников. Для отчётов и индексации задайте отдельную очередь или минимальную долю ресурсов. Приоритет без гарантированной квоты не является гарантией выполнения.
Непредсказуемое завершение процесса
Резкое завершение контейнера, исчерпание памяти и деплой во время обработки оставляют незавершённые задачи. Потребитель должен корректно завершать работу, переставать брать новые сообщения и давать текущей операции завершиться в пределах разумного таймаута.
Когда что выбирать
Начинайте с отдельных очередей по длительности и цене ошибки, а не с максимальной детализации. Быстрые уведомления и webhooks обычно выгодно держать отдельно от экспорта и пакетных пересчётов. Платежи и другие необратимые операции требуют отдельной политики повторов даже тогда, когда технически могут использовать тот же тип работников.
Приоритеты уместны, если потоков немного и правила старшинства понятны. Если срочная и фоновая работа имеют разные лимиты, таймауты или владельцев, отдельные очереди надёжнее. Если нужна строгая последовательность событий одного объекта, проектируйте ключ сериализации отдельно: порядок получения сообщений сам по себе не гарантирует порядок завершения.
Что делать дальше
Возьмите одну реальную очередь и составьте для неё короткую карточку: источник задачи, максимальный возраст, допустимое число повторов, условие успеха, действие при постоянной ошибке и метрики. Затем проведите тест с отключением потребителя посередине операции и с таймаутом внешнего API. Проверьте, не теряется ли задача, не создаётся ли дубль и можно ли найти причину по журналу.
После этого настройте алерты на возраст старейшего сообщения, долю ошибок и время обработки. Инвалидация кешей, гарантии доставки и выбор между RabbitMQ и Kafka — отдельные темы; здесь важнее сначала сделать поведение одной задачи предсказуемым.
Задачи: математика, физика, химия: Разбор задачи по шагам с проверкой и похожей задачей
Спросить за 10 ₽Источники: RabbitMQ Documentation: Queues, RabbitMQ Documentation: Dead Letter Exchanges
Частые вопросы
Нужно ли сразу создавать отдельную очередь для каждого типа задач?
Как избежать дублей при повторной доставке?
Что делать, если задача постоянно падает?
Можно ли увеличить число работников, если очередь растёт?
Что делать, если после обновления RabbitMQ или клиента очередь стала вести себя иначе?
Какие метрики нужны в продакшене?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.