ZIM-НИЙ SAAALEЗимние скидки: до −50% на старт и −20% на продление
до 31.01.2026 Подробнее
Выберите продукт

Очередь к upstream в Nginx: директива queue спасает бэкенд под пиками

Пики трафика способны положить любой бэкенд, если не ограничивать одновременные запросы. В Nginx это решается очередью к upstream: директива queue даёт backpressure, сглаживает bursts и предотвращает шторм ретраев.
Очередь к upstream в Nginx: директива queue спасает бэкенд под пиками

Если ваш бэкенд (PHP‑FPM, Node.js, Python, Java) периодически «залипает» под всплесками входящих запросов, а в логах видны волны 5xx и резкие скачки латентности, это классическая нехватка управляемого давления на уровень concurrency. У Nginx есть малозаметный, но крайне полезный инструмент — очередь к upstream через директиву queue. Она позволяет аккуратно дозировать одновременную нагрузку на серверы в группе и удерживать лишние запросы в короткой очереди, возвращая предсказуемые коды ошибок, если ждать уже нельзя.

Что такое backpressure на практике

Backpressure — это управляемое противодавление, которое вы создаёте на границе (в реверс‑прокси), чтобы бэкенд работал в своём комфортном окне параллелизма. Вместо того чтобы бездумно пропускать всё подряд в приложение (где начнутся блокировки, своп и таймауты), Nginx ограничивает одновременные соединения и, при необходимости, ставит новые запросы в очередь на короткое время.

Цель: ограничить одновременную обработку (concurrency) и дать краткосрочной очереди сгладить bursts, не превращая их в лавину ретраев и 5xx.

Как работает директива queue в upstream

Директива queue задаётся в контексте upstream и определяет максимальный размер очереди и время ожидания свободного соединения к бэкенду. Используется совместно с zone (общая память для состояния upstream между рабочими процессами) и часто — с ограничением одновременных подключений max_conns на серверах.

Логика простая:

  • Пока доступно свободное «место» по max_conns в группе — запросы немедленно идут в бэкенд.
  • Если все заняты, новые запросы помещаются в общую очередь (queue N), где ждут освобождения.
  • Если очередь переполнена — клиент получает немедленный отказ (обычно 503).
  • Если запрос простаивает в очереди дольше timeout — возвращается таймаут (обычно 504).

Ключевой плюс: вы заранее определяете границы — сколько параллельных запросов выдерживает приложение и сколько миллисекунд новая волна может постоять, прежде чем вы честно скажете «приходите позже».

Базовая конфигурация: минимальный рабочий пример

Ниже пример для HTTP‑бэкенда (например, Gunicorn/Node.js) на localhost:8080. Мы ограничиваем одновременные соединения через max_conns, включаем общий zone, активируем очередь queue и задаём небольшой timeout.

upstream app {
    zone app_zone 128k;
    least_conn;
    server 127.0.0.1:8080 max_conns=200;
    keepalive 100;
    queue 500 timeout=2s;
}

server {
    listen 80;
    access_log /var/log/nginx/app.access.log main;

    location / {
        proxy_pass http://app;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_connect_timeout 1s;
        proxy_read_timeout 15s;
        proxy_send_timeout 15s;
        proxy_buffering on;
    }

    error_page 503 /busy.html;
    error_page 504 /busy.html;

    location = /busy.html {
        internal;
        add_header Cache-Control no-store;
        return 200 "Please try again in a moment";
    }
}

Суть:

  • zone обязателен для корректной очереди и учёта max_conns между всеми воркерами.
  • max_conns задаёт «ширину горлышка»: ровно столько одновременных запросов пропускаем к приложению.
  • queue и timeout формируют короткую буферную зону для всплесков.
  • error_page даёт контролируемый ответ при отказе (вместо дефолтной ошибки).

Фрагмент конфигурации Nginx с очередью к upstream и лимитом max_conns.

PHP‑FPM/FCGI: что учесть

С FastCGI логика та же, но важно не забыть про реюз соединений к пулу и лимиты пула. В примере ниже pm.max_children в PHP‑FPM определяет верхнюю границу параллельно работающих воркеров; её и используем для max_conns.

upstream php_fpm {
    zone php_zone 128k;
    server unix:/run/php/php-fpm.sock max_conns=50;
    queue 200 timeout=1.5s;
}

server {
    listen 80;

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass php_fpm;
        fastcgi_keep_conn on;
        fastcgi_connect_timeout 1s;
        fastcgi_read_timeout 15s;
        fastcgi_send_timeout 15s;
        fastcgi_buffers 32 32k;
    }
}

Выставляя max_conns, ориентируйтесь на pm.max_children и реальные ресурсы CPU/памяти. Для PHP‑проектов дополнительно имеет смысл оптимизировать кэш кода и компрессию — примерный чек‑лист есть в материале про оптимизацию PHP и OPcache.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Расчёт параметров: откуда взять цифры

1) max_conns

Это ваш «конвейер». Возьмите число реальных воркеров приложения, умножьте на коэффициент 1.0–1.2 (при I/O‑bound нагрузке можно 1.5), но не больше того, что выдержит CPU/память без деградации. Если в пуле PHP‑FPM 50 детей, в Gunicorn 8 воркеров, в Node.js кластер на 4 процесса — берите эти числа и не пытайтесь «ускорить» систему расширением окна параллелизма в прокси. Оценивайте пределы машины, на которой крутится приложение, например вашего VDS.

2) queue

Очередь должна сглаживать короткие bursts, а не превращаться в «второй дата‑центр». Подход: возьмите пиковый входящий rps и хвост распределения времени обработки (p95/p99), оцените, сколько запросов за 1–2 секунды может «накопиться». Именно это число и задайте. Для средней витрины это часто 200–1000; для тяжёлых API — 50–200. Делайте замеры и не злоупотребляйте размерами.

3) timeout

Порог терпения пользователя короткий, а ретраи клиентов часто агрессивны. Лучше короткий таймаут очереди: 500 мс–2 с для веб‑страниц, 100–500 мс для низколатентных API. Чем больше timeout — тем выше риск «складирования» запросов с лавинообразной деградацией.

Быстрый способ прикинуть

Пример: есть 150 rps на пике, средняя обработка 80–120 мс, 95‑перцентиль 280 мс, стабильные 200 одновременных коннектов к приложению запускают сборку мусора и спайки. Тогда:

  • max_conns = 150 (под CPU),
  • queue = 300–400 (1–2 секунды накопления при bursts),
  • timeout = 1–1.5 s.

Финально подтверждаем на нагрузочном тесте и мониторинге задержек.

queue vs limit_req burst: что где уместно

Частая путаница: «у нас же есть limit_req с burst, зачем queue?» Эти механизмы о разном:

  • limit_req ограничивает входящую частоту запросов (rps), может отбросить избыток или допустить краткосрочный burst с задержкой.
  • queue работает на уровне доступа к бэкенду и контролирует именно параллелизм (concurrency). Она вступает в игру, когда все соединения к upstream заняты.

Лучший результат — комбинация. limit_req обрежет паразитные всплески на входе, а queue не даст добраться до приложения слишком большому числу одновременных запросов, которые всё же прошли через лимитер.

Коды ошибок и поведение при отказе

Когда очередь переполнена, Nginx сразу возвращает отказ (обычно 503). Если запрос простоял в очереди дольше лимита timeout, клиент получает таймаут (часто 504). Оба сценария нужно осмысленно обрабатывать: отдавайте лёгкую «busy»‑страницу, JSON‑ответ с кодом состояния для API и ставьте корректные заголовки кэширования, чтобы посредники не кэшировали такие ответы.

Ещё пара советов:

  • Не включайте «тяжёлые» обработчики на страницах с ошибками. Пусть это будет простой текст.
  • Логируйте факт переполнения очереди отдельно, чтобы быстро замечать деградацию.

Логи и метрики: как увидеть очередь

Для анализа стоит расширить формат логов. В современных релизах можно логировать время ожидания в очереди, если соответствующая переменная поддерживается сборкой. В любом случае полезно видеть общий $request_time, время апстрима $upstream_response_time и статус апстрима $upstream_status.

log_format main '$remote_addr $request $status $body_bytes_sent '
                 '$request_time $upstream_status $upstream_response_time';

Если у вашей сборки есть переменная для времени очереди, добавьте её рядом. При отсутствии — косвенно контролируйте хвосты: рост $request_time без соответствующего роста $upstream_response_time зачастую указывает на задержку до апстрима (включая очередь).

Дашборд мониторинга: снижение p95 после включения очереди в Nginx.

Важные тонкости и взаимодействие директив

  • Без zone очередь не работает как надо. Состояние занятости серверов и длина очереди должны быть общими для всех воркеров. Всегда объявляйте zone на upstream‑группе.
  • max_conns обязателен концептуально. Если его не задать, очередь мало что ограничит: Nginx будет считать сервер «свободным» почти всегда. Привязывайте лимит к воркерам приложения.
  • Балансировка. least_conn хорошо сочетается с очередью для неоднородных запросов. Для кэшируемых страниц бывает достаточно round robin по умолчанию.
  • Повторные попытки. Не перебарщивайте с proxy_next_upstream и ретраями клиента: ретраи поверх очереди легко превращаются в шторм.
  • Буферизация. Включённая proxy_buffering разгружает апстрим от медленных клиентов — особенно важно при пиках.
  • Кэш. Если значимая доля запросов кэшируема, микрокэш на Nginx уберёт десятки процентов нагрузки ещё до очереди.
  • HTTP/2 и очереди. Мультиплексирование не отменяет лимитов на апстрим. Очередь всё равно нужна.

Стратегия внедрения без сюрпризов

  1. Зафиксируйте текущие метрики: rps, p95/p99 времени ответа, одновременные подключения к приложению, ошибки 5xx.
  2. Определите безопасный max_conns из параметров пула/воркеров и CPU/памяти.
  3. Поставьте консервативные queue и timeout (например, 200–500 и 1–2 s).
  4. Разверните конфигурацию поэтапно (canary/blue‑green), проверьте логи.
  5. Покрутите параметры, добиваясь плоского профиля латентности и отсутствия очереди в спокойные периоды.
  6. Добавьте контролируемые «busy»‑страницы и оповещения при скачках 503/504.

Типичные ошибки

  • Слишком длинная очередь. Клиенты ждут по 5–10 секунд — UX страдает, ретраи множатся, а бэкенд всё равно умирает.
  • Отсутствие max_conns. Очередь не ограничивает параллелизм без потолка на соединения к апстриму.
  • Нереалистичные таймауты апстрима. proxy_read_timeout и др. должны соотноситься со временем работы бэкенда и timeout в очереди.
  • Нет общего zone. Воркеры видят картину по‑разному и очередь перестаёт быть глобальной.
  • Самообман «мы выдержим любой burst». Нагрузочные тесты быстро покажут обратное. Очередь — это про управляемый отказ, а не бесконечный буфер.

Связанные настройки, которые усиливают эффект

  • Тюнинг keepalive к апстриму. Держите достаточно «тёплых» соединений (keepalive в upstream и соответствующие настройки приложения), чтобы не тратить время на коннекты.
  • Разделение маршрутов. Вынесите тяжёлые API‑эндпоинты в отдельные upstream‑группы с собственными лимитами и очередью. Для статики — другой блок, зачастую без очереди.
  • Заголовки кэширования. Правильные Cache-Control и микрокэш снижают нагрузку до того, как запрос доберётся до очереди.

Проверка в бою: как понять, что стало лучше

  • Стабилизация p95/p99, исчезновение длинных хвостов в моменты пиков.
  • Ровная загрузка CPU и памяти бэкенда, отсутствие «залипаний» воркеров.
  • Редкие и предсказуемые 503/504 вместо случайной россыпи 502/499.
  • Снижение количества клиентских ретраев и сужение «лавины».

FAQ

Нужна ли очередь, если у меня есть autoscaling приложения?

Да. Автоскейлинг реагирует с задержкой. Очередь сгладит секунды/десятки секунд, пока раскатывается новый инстанс, и защитит текущие.

Можно ли ставить очередь только на часть запросов?

Да. Вынесите нужные локации в отдельный upstream с собственной queue и max_conns. Для лёгких эндпоинтов очередь не нужна вовсе.

Повлияет ли очередь на WebSocket/стриминг?

Очередь относится к моменту установления соединения с апстримом. Долго живущие соединения вынесите в отдельную группу и лимитируйте отдельно, иначе они «съедят» весь конвейер.

Какой размер зоны zone выбирать?

Для десятков/сотен серверов в группе достаточно десятков/сотен килобайт. Начните со 128k и увеличивайте при необходимости — это служебная память для состояния.

Итог

Директива queue в upstream — простой способ ввести дисциплину параллелизма и дать системе управляемый backpressure. Вместе с max_conns, умеренными таймаутами и грамотной буферизацией она превращает случайные пики в предсказуемые события: часть запросов будет вежливо отвергнута быстро, остальные — обработаны в нормальном режиме, без лавинообразных провалов. Добавьте к этому кэш и базовые лимиты по rps — и ваш стек начнёт «дышать» ровно даже под пиковые нагрузки.

Поделиться статьей

Вам будет интересно

memfd_create и «пропавшая» память: почему du не сходится с df и как найти deleted files через /proc, lsof и smaps OpenAI Статья написана AI (GPT 5)

memfd_create и «пропавшая» память: почему du не сходится с df и как найти deleted files через /proc, lsof и smaps

Если df показывает занятое место, а du «не видит» файлы, или RAM уходит без ясных причин, часто виноваты deleted files или shmem/m ...
Linux PSI: как измерять pressure CPU, памяти и диска и ловить latency spikes OpenAI Статья написана AI (GPT 5)

Linux PSI: как измерять pressure CPU, памяти и диска и ловить latency spikes

Linux PSI (Pressure Stall Information) показывает, сколько времени задачи реально «ждали» CPU, память или I/O. Разберём /proc/pres ...
Kubernetes: Pod завис в ContainerCreating — диагностика CNI, FailedMount и переполнения image filesystem OpenAI Статья написана AI (GPT 5)

Kubernetes: Pod завис в ContainerCreating — диагностика CNI, FailedMount и переполнения image filesystem

Pod может долго висеть в ContainerCreating из‑за сетевого плагина (CNI not initialized), проблем со storage (FailedMount, attach t ...