Настройка Prefetch в RabbitMQ для оптимальной производительности потребителей
Настройте prefetch в RabbitMQ так, чтобы потребители были заняты, не накапливали сообщения и не скрывали медленную обработку.
Настройка Prefetch в RabbitMQ для оптимальной производительности потребителей
Prefetch в RabbitMQ — это одна из тех настроек, которая выглядит незначительной, но кардинально меняет всё. Она контролирует, сколько неподтверждённых сообщений RabbitMQ может одновременно удерживать у потребителя. Установите слишком низкое значение — и быстрые потребители будут тратить слишком много времени на ожидание следующей доставки. Установите слишком высокое — и медленные потребители незаметно накопят работу, увеличат задержку, а графики глубины очередей станут обманчивыми.
Полезно думать о prefetch как о незавершённой работе. Prefetch, равный 20, означает, что потребитель может получить 20 сообщений, но ещё не подтвердить их. Эти сообщения больше не находятся в состоянии ready в очереди. Они unacked и находятся у потребителя до тех пор, пока он не подтвердит, не отклонит, не отвергнет или не отключится.
Это означает, что prefetch — это не просто регулятор пропускной способности. Это регулятор справедливости, памяти и восстановления после сбоев.
Что делает basic.qos в RabbitMQ
Потребители устанавливают prefetch с помощью basic.qos. В большинстве клиентских библиотек вы устанавливаете prefetch_count; prefetch_size используется редко и обычно оставляется равным нулю.
В Python с Pika:
channel.basic_qos(prefetch_count=10)
channel.basic_consume(
queue="jobs",
on_message_callback=handle_message,
auto_ack=False,
)
В Node.js с amqplib:
await channel.prefetch(10);
await channel.consume("jobs", async (msg) => {
try {
await handleMessage(msg.content);
channel.ack(msg);
} catch (err) {
channel.nack(msg, false, false);
}
}, { noAck: false });
Ручное подтверждение имеет значение. Если вы используете автоматические подтверждения, RabbitMQ считает сообщение завершённым сразу после доставки. Prefetch больше не защищает надёжность обработки таким же образом, потому что нет окна неподтверждённых сообщений для управления.
По умолчанию в современном использовании RabbitMQ применяет prefetch для каждого потребителя, даже если оригинальная формулировка AMQP ориентирована на каналы. Некоторые клиенты предоставляют флаг global. Будьте с ним осторожны. Общий канал или ограничение на уровне соединения могут создать запутанные взаимодействия между потребителями. Большинство сервисов проще анализировать, когда каждый потребитель имеет свой собственный канал и свой собственный prefetch count.
Почему prefetch влияет на задержку
Представьте очередь с двумя потребителями. Потребитель A получает пакет из 100 сообщений, а затем обращается к медленному внешнему API. Потребитель B здоров и быстр, но эти 100 сообщений уже назначены A. RabbitMQ не отдаст их B, пока A не отклонит их или его канал не закроется.
С точки зрения очереди, эти сообщения не готовы. С точки зрения пользователя, они задерживаются. Вот почему высокий prefetch может улучшить показатели системы в графиках брокера, но ухудшить реальную задержку.
Низкий prefetch даёт RabbitMQ больше возможностей для справедливого распределения работы. Высокий prefetch даёт потребителям больше локальной работы и меньше круговых запросов к брокеру. Ни то, ни другое не является всегда правильным.
Начальные значения, которые имеют смысл
Для медленных задач начинайте с малого. Если каждое сообщение вызывает сторонний API, записывает несколько строк в базу данных или выполняет ресурсоёмкие преобразования, попробуйте prefetch_count=1 до 10. Вы хотите, чтобы отказавший или медленный потребитель удерживал лишь небольшой объём работы.
Для средних задач, которые занимают десятки или сотни миллисекунд и выполняются на стабильных рабочих узлах, значения 10, 20 или 50 являются распространёнными отправными точками. Измеряйте, прежде чем увеличивать.
Для очень быстрых обработчиков, где брокер и потребитель находятся в сети с низкой задержкой, более высокий prefetch может уменьшить количество круговых запросов и повысить пропускную способность. Даже в этом случае избегайте выбора огромного числа только потому, что оно сделало бенчмарк хорошим на пять минут. Следите за памятью потребителя и хвостовой задержкой.
Простое эмпирическое правило — устанавливать prefetch примерно равным объёму работы, который потребитель может комфортно удерживать в течение короткого промежутка времени. Если рабочий обрабатывает около 20 сообщений в секунду, и вы готовы примерно к одной секунде локально буферизированной работы, prefetch около 20 — разумный эксперимент.
Как понять, что prefetch слишком высок
Prefetch, вероятно, слишком высок, когда:
messages_unacknowledgedвелико по сравнению с активными потребителями.- Некоторые потребители имеют много неподтверждённых сообщений, в то время как другие простаивают.
- Задержка сообщений высока, даже когда
messages_readyнизкое. - Память потребителя растёт во время всплесков.
- Сбой потребителя вызывает большую волну повторных доставок.
Последний пункт легко упустить. Если рабочий удерживает 1000 неподтверждённых сообщений и выходит из строя, RabbitMQ может повторно доставить эти сообщения. Это правильное поведение, но оно может создать дублирующее давление на нижестоящие системы, если обработчик не идемпотентен.
Снижение prefetch часто улучшает справедливость и поведение при восстановлении. Это может немного снизить пиковую пропускную способность, но может улучшить задержку, которую реально ощущают пользователи.
Как понять, что prefetch слишком низок
Prefetch, вероятно, слишком низок, когда:
- Потребители имеют низкую загрузку ЦП и низкое использование памяти, в то время как
messages_readyпродолжает расти. - Время обработки очень короткое, но скорость доставки ограничена.
- Задержка сети между потребителями и RabbitMQ заметна.
- Увеличение prefetch улучшает пропускную способность без увеличения хвостовой задержки или нагрузки на память.
Классический пример — быстрый рабочий, который выполняет небольшое вычисление в памяти и сразу подтверждает. При prefetch_count=1 он может тратить слишком много времени на ожидание следующего сообщения. Увеличение prefetch даёт ему небольшой локальный буфер и поддерживает его занятость.
Не скрывайте узкие места нижестоящих систем
Настройка prefetch не исправит медленную базу данных. Она может только изменить то, как распределяется и буферизируется работа. Если каждое сообщение ожидает один и тот же перегруженный API, более высокий prefetch может временно улучшить пропускную способность, одновременно увеличивая тайм-ауты и повторные попытки.
Измеряйте внутри потребителя. Регистрируйте или выводите метрики для времени, затраченного на декодирование сообщения, ожидание базы данных, вызов внешних сервисов и подтверждение. RabbitMQ может показать вам количество готовых и неподтверждённых сообщений, но не может сказать, почему ваш обработчик занимает восемь секунд.
Когда нижестоящий сервис ограничен по скорости, prefetch часто должен быть ниже, а не выше. Позвольте очереди видимо поглощать отставание вместо того, чтобы скрывать тысячи выполняемых вызовов внутри рабочих узлов.
Prefetch и параллелизм — это разные вещи
Prefetch, равный 50, не означает автоматически, что ваш потребитель обрабатывает 50 сообщений параллельно. Это означает только, что RabbitMQ может доставить 50 сообщений до получения подтверждений. Будут ли они выполняться одновременно, зависит от вашего кода потребителя.
Однопоточный потребитель с prefetch 50 может обрабатывать одно сообщение за раз, в то время как 49 ожидают в памяти. Пул рабочих с параллелизмом 10 и prefetch 50 может держать десять задач активными и сорок буферизированными. Иногда этот буфер полезен. Иногда это просто задержка.
Сопоставляйте prefetch с фактическим параллелизмом. Если ваш процесс может выполнять пять обработчиков одновременно, prefetch от 5 до 20 легче анализировать, чем 500.
Компромиссы порядка и справедливости
Очереди RabbitMQ сохраняют порядок на уровне очереди, но поведение потребителя может изменить порядок завершения работы. При нескольких потребителях и prefetch больше 1 сообщение 20 может завершиться раньше сообщения 3, потому что оно попало к более быстрому рабочему или имело более лёгкую работу.
Для большинства рабочих очередей порядок завершения не имеет значения. Для обновлений учётных записей, изменений инвентаря или рабочих процессов, которые должны обрабатываться последовательно, это может иметь большое значение. В таких случаях использование одной очереди на ключ упорядочивания, шардирование по ключу или поддержание низкого prefetch может быть безопаснее, чем погоня за максимальной пропускной способностью.
Справедливость имеет аналогичный компромисс. Низкий prefetch позволяет RabbitMQ распределять работу более равномерно, потому что потребители чаще возвращаются за сообщениями. Высокий prefetch вознаграждает потребителей, которые получают сообщения первыми. Если сообщения имеют неравномерное время обработки, это может привести к тому, что один рабочий будет удерживать кучу медленных задач, в то время как другой быстро завершит свой пакет.
Когда говорят, что "балансировка нагрузки RabbitMQ неравномерна", prefetch — одна из первых вещей, которые стоит проверить. Брокер может балансировать только те сообщения, которые ещё не были доставлены.
Поведение при сбоях имеет значение
Prefetch изменяет то, что происходит, когда потребитель умирает. При prefetch_count=1 одна неподтверждённая доставка возвращается при закрытии канала. При prefetch_count=500 могут вернуться сразу сотни. Если потребитель выполнил частичные побочные эффекты перед сбоем, эти повторные доставки могут вызвать дублирующие записи, дублирующие электронные письма или дублирующие вызовы API, если обработчик не идемпотентен.
Это не означает, что высокий prefetch неправильный. Это означает, что высокий prefetch подходит для идемпотентных обработчиков, чётких правил повторных попыток и мониторинга частоты повторных доставок. Если дублирующая обработка была бы опасна, держите окно неподтверждённых сообщений маленьким, пока приложение не будет построено для обработки этого.
Посмотрите на флаг redelivered в потребителе. Это не полный счётчик повторных попыток, но это полезный сигнал о том, что сообщение было доставлено ранее. Для надёжных ограничений повторных попыток отслеживайте попытки в заголовках или в состоянии приложения и направляйте исчерпанные сообщения в очередь недоставленных сообщений.
Несколько очередей и смешанные рабочие нагрузки
Одно значение prefetch редко подходит для всех очередей. Сервис, который потребляет thumbnail.generate и email.send, может нуждаться в разных настройках для каждой. Генерация миниатюр может быть ресурсоёмкой по ЦП и лучше всего работать с низким параллелизмом. Отправка электронной почты может быть ограничена сетью и допускать больше выполняемых сообщений.
Если один процесс потребляет несколько очередей на одном канале, поведение QoS может стать сложнее для анализа. Предпочитайте отдельные каналы для существенно разных рабочих нагрузок. Это делает prefetch, мониторинг и обработку сбоев более очевидными.
Смешанные размеры сообщений — ещё один предупредительный знак. Если очередь содержит как крошечные события, так и огромные полезные нагрузки, prefetch на основе количества не отражает нагрузку на память. Десять маленьких сообщений и десять больших сообщений — это не одно и то же. В этой ситуации разделите рабочую нагрузку или переместите большие полезные нагрузки из RabbitMQ и передавайте ссылки.
Следите за unacked на потребителя, а не только на очередь
Количество неподтверждённых сообщений на уровне очереди говорит вам о наличии незавершённой работы, но может скрывать перекос. Один потребитель может удерживать большую часть неподтверждённых сообщений, в то время как остальные почти пусты. Это часто указывает на высокий prefetch, неравномерную стоимость сообщений или один нездоровый рабочий.
Используйте метрики на уровне потребителя из интерфейса управления, Prometheus или rabbitmqctl list_consumers во время теста. Если распределение неравномерно, снижение prefetch или разделение медленных типов сообщений может улучшить реальную задержку, даже если общая пропускная способность изменится лишь незначительно.
Пересматривайте prefetch после развёртываний
Значения prefetch устаревают. Значение, которое работало, когда обработчик записывал только одну строку базы данных, может оказаться неправильным после следующего релиза, добавляющего вызов API, дополнительную проверку или большую полезную нагрузку. Относитесь к prefetch как к части конфигурации производительности, а не как к числу, которое вы устанавливаете один раз и забываете.
После релиза потребителя сравните задержку обработки, количество неподтверждённых сообщений, повторные доставки и память потребителя с предыдущей версией. Если задержка растёт, но ЦП не насыщен, обработчик может ожидать чего-то внешнего, и более низкий prefetch может сохранить систему более справедливой. Если ЦП высок, и каждое сообщение ограничено по ЦП, добавление рабочих или уменьшение работы на сообщение может иметь большее значение, чем изменение prefetch.
Документируйте причину выбранного значения рядом с конфигурацией потребителя. Будущие сопровождающие должны знать, был ли prefetch_count=5 выбран для справедливости, памяти, упорядочивания, ограничений скорости нижестоящих систем или просто как временное значение по умолчанию.
Тестируйте с реальными формами сообщений
Не настраивайте prefetch с крошечными фиктивными сообщениями, если производственные сообщения представляют собой большие JSON-полезные нагрузки или включают дорогие запросы к базе данных. Размер сообщения и стоимость обработчика имеют значение.
Полезный цикл тестирования:
- Выберите значение prefetch.
- Запустите реалистичную скорость публикации на время, достаточное для наблюдения устойчивого поведения.
- Следите за
messages_ready,messages_unacknowledged, ЦП потребителя, памятью потребителя, задержкой обработки и частотой ошибок. - Убейте одного потребителя и посмотрите, сколько сообщений будет повторно доставлено.
- Увеличьте или уменьшите prefetch и повторите.
Лучшее значение редко является тем, которое даёт самую высокую краткосрочную пропускную способность в бенчмарке. Это значение, которое поддерживает занятость потребителей, приемлемую задержку и отказ таким образом, с которым ваша система может справиться.
Практическое значение по умолчанию
Если у вас ещё нет данных, начните с ручных подтверждений и prefetch_count=10 для обычных рабочих очередей. Используйте 1 для медленной, дорогой или строго справедливой обработки. Попробуйте 20 или 50 для быстрых, стабильных обработчиков после измерения. Увеличивайте только тогда, когда метрики показывают, что круговые запросы доставки являются узким местом, и у потребителей есть запас памяти.
Настройка prefetch в RabbitMQ — это не разовая настройка. Пересматривайте её, когда меняется размер сообщения, код потребителя, зависимости нижестоящих систем или когда вы добавляете больше экземпляров рабочих. Правильное значение prefetch — это то, которое соответствует текущей форме работы.