Производительность и тюнинг под нагрузку

Рейтинг: 68.5% · 14 голосов
Самый подробный курс по Nginx - от первого конфига до production API-gateway. Архитектура и event loop, контексты и location, виртуальные хосты, статика, reverse proxy и upstream-балансировка, кэширование, HTTPS и TLS (post-quantum), HTTP/2 и HTTP/3 QUIC, безопасность и rate limiting, realip, JSON-логи и сквозная трассировка, метрики, тюнинг и траблшутинг. Плюс OpenResty (Lua, фазы, JWT, динамика) и Angie (нативные Prometheus, ACME, dynamic upstream). На основе эталонного gateway и лучших практик. Для новичков и SRE/DevOps. Актуально на 2026 (nginx 1.30/1.31, Angie, ingress-nginx EOL -> Gateway API).
Ответить
Аватара пользователя
Igor_NgINX
Сообщения: 44
Зарегистрирован: 11 май 2026, 05:31

Производительность и тюнинг под нагрузку

Сообщение Igor_NgINX »

Оглавление курса (44)
  1. Что такое Nginx и почему он захватил веб
  2. Установка Nginx, Angie и OpenResty: пакеты, версии, Docker
  3. Первый конфиг с нуля: минимальный сервер за 5 минут
  4. Архитектура процессов: master, worker и event loop
  5. Анатомия конфигурации: контексты, наследование и модульность
  6. Виртуальные хосты: server и server_name
  7. Директива location: матчинг и приоритеты по шагам
  8. Раздача статики: root, alias, try_files, sendfile
  9. Переменные, map и блок if
  10. rewrite, return и канонизация URL
  11. Работа с заголовками: add_header и proxy_set_header
  12. Модель доверия прокси-заголовков: realip и защита от спуфинга
  13. Сжатие: gzip, brotli и zstd
  14. Reverse proxy: proxy_pass и проброс запроса
  15. upstream и балансировка нагрузки
  16. Таймауты, повторы и устойчивость прокси
  17. FastCGI и PHP: nginx + PHP-FPM (и uwsgi/scgi)
  18. WebSocket, gRPC и стриминг через Nginx
  19. Кэширование ответов: proxy_cache и микрокэш
  20. HTTPS и TLS: сертификаты, протоколы, шифры (2026)
  21. Let's Encrypt и ACME: certbot, acme.sh, нативный ACME Angie
  22. HTTP/2 и HTTP/3 (QUIC) в 2026
  23. Безопасность: заголовки, скрытие версии, ограничения
  24. Rate limiting и защита от перегрузки
  25. Контроль доступа: allow/deny, auth_basic, auth_request и mTLS
  26. Логирование: access_log, форматы и структурированный JSON
  27. Сквозная трассировка запросов и OpenTelemetry
  28. Метрики и мониторинг Nginx
  29. Производительность и тюнинг под нагрузку (вы здесь)
  30. Траблшутинг: 502, 504, 403, 404 и debug-лог
  31. OpenResty: Nginx как платформа на LuaJIT
  32. Фазы обработки запроса и Lua-хуки
  33. Lua API: ngx.*, shared dict, cosocket и lua-resty-core
  34. Практика OpenResty: авторизация, JWT, кэш, Redis
  35. Angie: современный форк Nginx 2026
  36. Динамическая конфигурация и service discovery
  37. Nginx как API-gateway и Kubernetes Gateway API
  38. Динамические модули, njs и WAF
  39. Stream-модуль: проксирование TCP и UDP
  40. Деплой, перезагрузка и конфиг как код
  41. Сценарий: статика, SPA и кэширование за CDN
  42. Сценарий: микросервисный gateway по образцу ngx-trace-gateway
  43. Капстоун: собираем production-gateway с нуля
  44. Чек-лист безопасности, CVE 2026 и аудит конфигурации
Сервер держал 2000 rps, ты выкатил рекламу, прилетело 8000 - и nginx начал отдавать 502, а в логах поползли "worker_connections are not enough". Первый инстинкт - крутить цифры наугад: поднять worker_connections до миллиона, влить десяток sysctl из чужого гайда, выставить gzip 9. Через час становится только хуже: память кончилась, CPU забит компрессией, а latency не упала. Это классическая ловушка. Тюнинг вслепую - это не оптимизация, это гадание.

В этом уроке разберём nginx тюнинг как инженерную дисциплину: сначала измеряем, потом меняем одну вещь, снова измеряем. Пройдём по всей цепочке узких мест - воркеры, соединения, ядро, файлы, буферы, TLS, кэш - и в конце соберём чек-лист, по которому ты выжмешь из железа максимум без шаманства. Тема большая, поэтому держим темп: nginx производительность складывается из десятка слоёв, и каждый слой может стать бутылочным горлышком.

Главный принцип: сначала измерь, потом тюнь

Запомни это до того, как тронешь хоть одну директиву. У тебя есть три типа узких мест: CPU, IO (диск/сеть) и сам код бэкенда. nginx оптимизация одного слоя бессмысленна, если бутылочное горлышко в другом. Поднимать worker_connections, когда упирается диск - всё равно что расширять въезд на парковку, у которой забит выезд.

Как понять, где жмёт? Смотри метрики, не догадки.

Код: Выделить всё

top              # CPU воркеров. Если nginx ест 100% одного ядра - упор в CPU (часто TLS или gzip)
iostat -x 1      # %util диска. Близко к 100 - упор в IO
ss -s            # сводка по сокетам, сколько в TIME-WAIT
ss -ntl          # слушающие сокеты и их Recv-Q (переполнение accept-очереди)
Ключевая идея урока: nginx highload - это не один волшебный параметр, а отсутствие узких мест в цепочке. Ты двигаешься по цепочке, находишь самый узкий участок, расширяешь его - и узким становится следующий. Бесконечно крутить уже широкие места нет смысла.

Изображение

Воркеры и соединения: фундамент под нагрузку

nginx - это набор воркер-процессов, каждый из которых обрабатывает тысячи соединений в одном потоке через epoll. Базовая настройка:

Код: Выделить всё

# в главном контексте nginx.conf
worker_processes      auto;          # по числу ядер CPU, не больше
worker_rlimit_nofile  65535;         # лимит файловых дескрипторов на воркер

events {
    worker_connections 10240;        # одновременных соединений на один воркер
    multi_accept       on;           # забирать все готовые соединения за раз
    use                epoll;        # на Linux - явно epoll
}
Разберём по косточкам. worker_processes auto ставит число воркеров по числу ядер. Больше - вредно: лишние процессы дерутся за CPU и кэш. На контейнере с лимитом в 2 ядра ставь 2, не auto от хоста с 64 ядрами.

worker_connections 10240 - сколько соединений тянет один воркер. Тут главная грабля новичков: это НЕ число клиентов. Когда nginx проксирует, на одного клиента уходит два дескриптора - сокет к клиенту и сокет к апстриму. Плюс расход на статику, кэш, логи. Реальный потолок клиентов при проксировании - примерно worker_processes * worker_connections / 2.

worker_rlimit_nofile обязан быть больше worker_connections, иначе воркер упрётся в лимит ОС раньше, чем в свой лимит соединений, и ты получишь "Too many open files" в error_log. Эмпирика: ставь его в 2-4 раза больше worker_connections. И помни - этот лимит сверху ограничен системным (проверь "cat /proc/sys/fs/file-max" и ulimit для процесса).

Если в логах появилось "worker_connections are not enough" - это сигнал nginx worker_connections поднять и параллельно проверить, не утекают ли соединения к медленному апстриму.

Сеть и ядро: sysctl, reuseport и accept-очередь

nginx живёт поверх TCP-стека ядра. Можно идеально настроить воркеры, но если ядро роняет соединения на входе - всё впустую. При высоком rps первым переполняется accept backlog: очередь установленных соединений, которые ждут, когда воркер сделает accept().

Код: Выделить всё

# /etc/sysctl.d/99-nginx.conf
net.core.somaxconn        = 65535   # потолок accept-очереди (по умолчанию мизерные 4096)
net.core.netdev_max_backlog = 65535 # очередь пакетов от сетевухи к стеку
net.ipv4.tcp_max_syn_backlog = 65535 # очередь полуоткрытых (SYN) соединений
net.ipv4.tcp_tw_reuse     = 1       # переиспользовать сокеты в TIME-WAIT для исходящих
net.ipv4.ip_local_port_range = "1024 65535"  # больше эфемерных портов к апстримам
fs.file-max               = 2097152 # глобальный потолок дескрипторов
Применить: sysctl -p /etc/sysctl.d/99-nginx.conf. Но somaxconn в ядре - только половина дела. nginx должен сам запросить большой backlog в директиве listen, иначе возьмёт дефолтные 511:

Код: Выделить всё

server {
    listen 443 ssl reuseport backlog=65535;
    http2  on;
    # ...
}
reuseport - это отдельный козырь для nginx highload. Без него все воркеры висят на одном слушающем сокете и дерутся за accept под общим замком - на десятках ядер это видимый thundering herd. С reuseport ядро создаёт отдельную очередь на каждый воркер и само раскидывает по ним соединения через SO_REUSEPORT. На многоядерных машинах под высоким rps это даёт заметный прирост и ровнее распределяет нагрузку. Указывай reuseport ровно один раз на listen-пару (адрес:порт), не дублируй в нескольких server-блоках на тот же порт.

Грабля: про somaxconn забывают, ставят backlog=65535 в nginx, а ядро молча режет его до своих 4096. Поднимай оба, и обязательно проверь Recv-Q слушающего сокета через "ss -ntl" - ненулевые значения там означают, что accept-очередь переполняется и соединения теряются.

Keepalive: дешёвый множитель, который часто забывают

Установка TCP-соединения и TLS-хендшейк - дорогие. Если на каждый запрос их делать заново, ты сжигаешь CPU и латентность на ровном месте. Keepalive переиспользует соединения. Есть две стороны - клиент и апстрим, и их часто путают.

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

Код: Выделить всё

http {
    keepalive_timeout  65s;      # держать клиентское соединение открытым
    keepalive_requests 10000;    # запросов на одно соединение, потом переоткрыть
}
keepalive_requests по умолчанию всего 1000 - для API под нагрузкой это мало, соединение переоткрывается слишком часто. Подними до 10000. keepalive_timeout балансируй: большой - держит много полудохлых соединений и жрёт дескрипторы, маленький - заставляет клиента переустанавливать связь. 30-65 секунд - разумная вилка.

Теперь апстрим - и тут важное обновление 2026 года. Начиная с nginx 1.29.7 (вошло в stable 1.30) соединение к бэкенду по умолчанию идёт по HTTP/1.1 с keepalive. Раньше дефолтом был HTTP/1.0 с закрытием соединения после каждого запроса - и это годами било по производительности у тех, кто не знал. Теперь из коробки лучше. Но для совместимости со старыми версиями и для явности всё равно держи в блоке upstream:

Код: Выделить всё

upstream backend {
    server 10.0.0.1:9000;
    server 10.0.0.2:9000;
    keepalive 64;                # пул idle-соединений к апстриму на воркер
}

server {
    location /api/ {
        proxy_pass http://backend;
        proxy_http_version 1.1;          # обязательно для keepalive к апстриму
        proxy_set_header Connection "";  # убрать заголовок close
    }
}
Директива keepalive 64 в upstream - это число idle-соединений, которые воркер держит про запас. Без неё пул не работает вообще, даже на 1.30. В 1.30 появился ещё параметр keepalive ... local для привязки соединений к локальному адресу. Прикинь размер пула: если каждый воркер держит keepalive 64 к каждому из апстримов - это сотни исходящих соединений, под них и нужен расширенный ip_local_port_range из sysctl выше.

Файлы, буферы и сжатие: где утекают CPU и память

Отдача статики. Тут работают три директивы в связке:

Код: Выделить всё

http {
    sendfile        on;     # отдавать файл из page cache в сокет без копий в userspace
    tcp_nopush      on;     # копить данные и слать полными пакетами (только с sendfile)
    tcp_nodelay     on;     # для keepalive - не задерживать мелкие пакеты (алгоритм Нейгла)

    open_file_cache          max=10000 inactive=60s;  # кэш дескрипторов и метаданных
    open_file_cache_valid    60s;
    open_file_cache_min_uses 2;
    open_file_cache_errors   on;
}
sendfile on отдаёт файл напрямую из page cache ядра в сокет, минуя копирование в память nginx - меньше CPU и переключений контекста. tcp_nopush работает только вместе с sendfile: nginx накапливает данные и шлёт их полными сегментами вместо кучи мелких пакетов. tcp_nopush и tcp_nodelay не конфликтуют - nginx умно комбинирует: копит тело, но последний пакет шлёт сразу.

open_file_cache держит открытые дескрипторы и метаданные статических файлов в памяти. Под высоким rps статики это убирает тысячи лишних syscall open()/stat() в секунду. Грабля: при inactive 60s обновлённый на диске файл может отдаваться из кэша до минуты - на проде с частыми деплоями статики уменьшай или сбрасывай кэш.

Для отдачи больших файлов (видео, дистрибутивы) sendfile блокирует воркер на время чтения с диска - один медленный диск тормозит всех клиентов воркера. Лечится асинхронным IO через thread pool:

Код: Выделить всё

aio threads;                 # читать с диска в отдельном пуле потоков, не блокируя воркер
directio 16m;                # файлы больше 16м читать в обход page cache
output_buffers 2 1m;
Буферы - это всегда баланс память против производительности. Слишком маленькие proxy-буферы заставляют nginx писать ответ апстрима во временный файл на диск (буферизация на диск = тормоза). Слишком большие - жрут память на каждом соединении, а соединений тысячи.

Код: Выделить всё

client_body_buffer_size   16k;
client_header_buffer_size 1k;
client_max_body_size      10m;     # отбивать гигантские аплоады
proxy_buffer_size         16k;     # под заголовки ответа апстрима
proxy_buffers             16 16k;  # под тело ответа: 16 буферов по 16к = 256к на соединение
proxy_busy_buffers_size   32k;
Прикидывай математику: proxy_buffers 16 16k - это до 256 КБ на активное проксируемое соединение. На 10000 соединений - 2.5 ГБ только под буферы. Не раздувай вслепую.

Сжатие - чистый размен CPU на трафик. gzip_comp_level от 1 до 9, но кривая нелинейная: с 1 до 5 размер падает заметно, а с 5 до 9 - почти не меняется, зато CPU растёт резко. Под нагрузкой держи уровень 4-5, не 9.

Код: Выделить всё

gzip            on;
gzip_comp_level 5;          # не 9 - на больших уровнях CPU не окупается
gzip_min_length 1024;       # мелочь сжимать невыгодно
gzip_vary       on;
gzip_types      text/plain text/css application/json application/javascript application/xml;
Помни 2026: нативный Zstd (zstd on) есть только в Angie - он сжимает лучше gzip при меньшем CPU. Brotli в ванильном nginx - сторонний динамический модуль ngx_brotli, не встроен.

TLS под нагрузкой: ECDSA вместо RSA и кэш сессий

TLS-хендшейк - одна из самых прожорливых операций по CPU. Два рычага дают наибольший эффект.

Первый - кэш TLS-сессий. Полный хендшейк делает асимметричную криптографию (дорого). Возобновлённая сессия из кэша - дёшево. Включай разделяемый кэш:

Код: Выделить всё

ssl_session_cache    shared:SSL:50m;   # ~200000 сессий, общий для всех воркеров
ssl_session_timeout  1d;
ssl_session_tickets  off;              # тикеты без ротации ключей - риск forward secrecy
ssl_protocols        TLSv1.2 TLSv1.3;
Второй рычаг недооценивают: тип сертификата. ECDSA P-256 сильно дешевле по CPU на хендшейке, чем RSA 2048/4096 - подпись эллиптической кривой считается в разы быстрее. Под высоким rps с обрывами keepalive это прямой выигрыш по CPU и latency. Можно отдавать оба сертификата сразу (dual-cert), nginx выберет по возможностям клиента:

Код: Выделить всё

ssl_certificate     /etc/ssl/ecdsa/fullchain.pem;   # современные клиенты возьмут ECDSA
ssl_certificate_key /etc/ssl/ecdsa/privkey.pem;
ssl_certificate     /etc/ssl/rsa/fullchain.pem;      # legacy-клиенты - RSA
ssl_certificate_key /etc/ssl/rsa/privkey.pem;
Важные нюансы 2026. ssl_ciphers НЕ применяется к TLS 1.3 - для него шифры задаются через ssl_conf_command Ciphersuites. ssl_dhparam нужен только для DHE-шифров; с TLS 1.3 идёт ECDHE и dhparam часто не нужен - не копируй вслепую тяжёлую генерацию. Под пост-квантовый обмен на OpenSSL 3.5+ ставь ssl_ecdh_curve X25519MLKEM768:X25519:prime256v1. OCSP stapling (ssl_stapling) практически мёртв для Let's Encrypt - они убрали OCSP-URL в мае 2025, считай это legacy. За эталоном настроек иди в Mozilla SSL Configuration Generator, а не копируй чужой конфиг из 2018-го.

Кэш как множитель и нагрузочное тестирование

Самый дешёвый запрос - тот, что не дошёл до бэкенда. Кэш ответов проксирования - это не оптимизация на проценты, это множитель в разы. Запрос из кэша отдаётся за микросекунды без участия апстрима и БД.

Код: Выделить всё

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=app:128m
                 inactive=60m max_size=10g use_temp_path=off;

location /api/ {
    proxy_pass http://backend;
    proxy_cache app;
    proxy_cache_valid 200 1h;
    proxy_cache_lock  on;                  # против dog-pile: один запрос греет кэш, остальные ждут
    proxy_cache_background_update on;       # отдавать просрочку, обновляя в фоне
    proxy_cache_use_stale error timeout updating;  # при сбое апстрима отдать устаревшее
    add_header X-Cache-Status $upstream_cache_status always;  # HIT/MISS/EXPIRED в ответе
}
proxy_cache_lock on решает проблему dog-pile (cache stampede): когда популярный ключ протух, без локa все одновременные запросы ринутся на апстрим разом и положат его. С локом первый идёт за свежим ответом, остальные ждут его. proxy_cache_use_stale превращает кэш ещё и в подушку безопасности: упал бэкенд - клиент получит устаревший, но валидный ответ вместо 502.

Теперь главное - как понять, что тюнинг сработал. Только числами. И мерить надо НЕ среднее, а перцентили. Среднее (mean) прячет хвост: при среднем 50 мс твой p99 может быть 3 секунды, и именно эти 1% самых медленных запросов - это разъярённые пользователи. p50 (медиана) - типичный запрос, p95 - почти все, p99 - худший процент, где живёт реальная боль.

Код: Выделить всё

# wrk: 4 потока, 200 соединений, 30 секунд, с распределением latency
wrk -t4 -c200 -d30s --latency https://example.com/api/

# фрагмент вывода:
#   Latency Distribution
#      50%   12.30ms
#      75%   18.40ms
#      90%   31.10ms
#      99%  145.00ms      <- хвост: вот сюда смотри
#   Requests/sec:  16240.55
Читаем: если rps уперся в потолок, а CPU воркеров на 100% - упор в CPU (смотри TLS и gzip). Если rps низкий, а CPU и диск свободны - узкое место в апстриме или в сети. Если p50 нормальный, а p99 улетел - где-то периодическая блокировка: сборка мусора на бэкенде, медленный диск под sendfile, переполнение accept-очереди.

k6 удобнее для сценариев со сложной логикой и порогами:

Код: Выделить всё

import http from 'k6/http';
export const options = {
  vus: 200, duration: '30s',
  thresholds: { http_req_duration: ['p(95)<200', 'p(99)<500'] },  // тест упадёт, если хуже
};
export default function () {
  http.get('https://example.com/api/');
}
ab (ApacheBench) сгодится для быстрой грубой прикидки, но он однопоточный и плохо считает хвост - для серьёзного nginx highload бери wrk или k6. И всегда гоняй нагрузку с отдельной машины: если генератор нагрузки крутится на том же хосте, что и nginx, ты меряешь не сервер, а драку за CPU.

Мини-лаба: тюнинг под высокий rps по шагам

Повтори руками. Нужен nginx и вторая машина (или контейнер) под генератор нагрузки.
  • Сними базовую линию ДО любых правок: с отдельной машины запусти wrk -t4 -c200 -d30s --latency на свой endpoint. Запиши Requests/sec, p50, p95, p99. Это твоя точка отсчёта.
  • Параллельно на сервере держи открытыми top (CPU воркеров) и "ss -ntl" (Recv-Q слушающего сокета). Определи по ним, где жмёт: CPU, accept-очередь или апстрим.
  • Поправь воркеры: worker_processes auto, worker_rlimit_nofile 65535, worker_connections 10240, multi_accept on. Сделай nginx -t и nginx -s reload. Прогони wrk снова, сравни числа.
  • Включи keepalive к апстриму: в upstream добавь keepalive 64, в location - proxy_http_version 1.1 и proxy_set_header Connection "". Подними keepalive_requests 10000. Reload, замерь - latency хвоста должна просесть.
  • Подними ядро: somaxconn и tcp_max_syn_backlog до 65535 через sysctl, добавь backlog=65535 и reuseport в listen. Reload, замерь - под высоким -c рост rps будет заметнее всего.
  • Сравни RSA и ECDSA: прогони wrk по HTTPS-endpoint с RSA-сертификатом, потом переключи на ECDSA, снова прогони. Посмотри на CPU воркеров и rps на хендшейках.
  • Включи proxy_cache на кэшируемый GET. Прогони wrk - rps по кэшируемому пути должен подскочить в разы, апстрим почти не нагружен. Проверь заголовок X-Cache-Status: HIT.
  • Меняй по ОДНОМУ параметру за прогон. Если поменял три вещи разом и стало лучше - ты не знаешь, какая из них помогла, а какая навредила.
Контрольные вопросы
  • Почему реальный потолок клиентов при проксировании - это примерно (worker_processes * worker_connections) / 2, а не worker_processes * worker_connections?
  • Что изменилось в nginx 1.30 в поведении соединения к апстриму по умолчанию и почему директива keepalive в upstream всё равно обязательна?
  • Ты выставил backlog=65535 в listen, но соединения под нагрузкой всё равно теряются. Какой sysctl ты забыл и как это проверить?
  • Почему при тюнинге надо смотреть на p99, а не на среднее время ответа, и о чём говорит ситуация "p50 в норме, p99 улетел в небо"?
Итог

nginx производительность - это не один волшебный флаг, а отсутствие узких мест в цепочке: воркеры -> ядро/сеть -> keepalive -> файлы и буферы -> TLS -> кэш. Чек-лист тюнинга: worker_processes auto и worker_rlimit_nofile; keepalive к клиенту и к апстриму (помни про дефолт HTTP/1.1 с 1.30); sysctl somaxconn и backlog с reuseport; разумные буферы под твою память; sendfile и open_file_cache на статике; TLS с session cache и ECDSA вместо RSA; кэш как множитель. И главное правило, без которого весь список вредит: сначала измерь wrk или k6 по p95/p99, поменяй ОДНУ вещь, измерь снова. Не тюнь вслепую - меряй.
👍1 ❤️3 🔥 😄 🤔1
Аватара пользователя
bunadmin
Сообщения: 1
Зарегистрирован: 01 июн 2026, 09:47

Re: Производительность и тюнинг под нагрузку

Сообщение bunadmin »

Про дефолт keepalive в 1.30 вообще не знал, годами руками прописывал proxy_http_version 1.1 и думал что без этого никак. Спасибо, теперь понятно почему оно и так начало работать после апгрейда.
👍 ❤️ 🔥 😄 🤔
Аватара пользователя
softgoblin
Сообщения: 1
Зарегистрирован: 31 май 2026, 19:51

Re: Производительность и тюнинг под нагрузку

Сообщение softgoblin »

Поднял worker_connections до 100000 как в одном гайде и сервер начал свопиться. Дошло только сейчас что упор был в апстрим а не в соединения. Сначала измерь - запомнил.
👍 ❤️2 🔥 😄 🤔
Ответить
← Предыдущая глава
Метрики и мониторинг Nginx
Следующая глава →
Траблшутинг: 502, 504, 403, 404 и debug-лог

Все главы курса «Nginx профессионально: от первого конфига до API-gateway на OpenResty и Angie»

Поделиться темой: ✈ Telegram VK
Похожие запросы: Тюнинг и производительность nginx

Вернуться в «Nginx профессионально: от первого конфига до API-gateway на OpenResty и Angie»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 2 гостя