# теория · шаг 2 из 4
Сколько воркеров ставить
Коротко
Число процессов и предел соединений задаются разными директивами.
- worker_processes находится в главном контексте; auto выбирает число процессов по доступным ядрам.
- worker_connections задаётся внутри events и ограничивает соединения одного воркера.
- Проксирование обычно занимает соединение с клиентом и соединение с бэкендом. Поэтому произведение настроек не равно числу одновременно обслуживаемых клиентов.
- Файловый дескриптор - номер открытого файла или сокета в процессе. Число доступных дескрипторов тоже ограничено. Сверяй настройки с метриками и сообщениями error_log.
Если worker_connections и nofile - знакомая пара, листай до «Теперь сам».
Сколько клиентов выдержит вот этот конфиг
worker_processes 4;
events {
worker_connections 4096;
}
Наивный ответ: 4 × 4096 = 16 384 клиента.
Правильный ответ: зависит от того, чем занят nginx. Если он отдаёт файлы с диска - да, около шестнадцати тысяч. Если он работает обратным прокси - вдвое меньше, примерно восемь тысяч. Почему так - дальше.
На что это похоже
Call-центр, у оператора на пульте сорок линий.
Пока оператор просто отвечает на вопросы, сорок линий - это сорок клиентов. Но если по каждому звонку он сам набирает бухгалтерию и держит обе трубки, то на одного клиента уходит две линии, и клиентов помещается двадцать.
worker_connections - это линии на пульте, а не люди на другом конце. Считать
надо соединения, и в режиме прокси их на каждого клиента два: одно с клиентом,
второе с бэкендом.
Механизм: что считается соединением
Слот worker_connections тратится не только на клиентов. В счёт идут:
- соединение с клиентом - всегда;
- соединение с бэкендом - когда nginx проксирует;
- слушающие сокеты (
listen) - по одному на каждый, немного, но они есть; - соединения, которые клиент держит открытыми после ответа (keepalive), - они ещё живы и слот занимают.
Последний пункт неочевиден и на практике съедает больше всего: браузер удерживает соединение секунды после загрузки страницы, чтобы не устанавливать его заново.
Правило
worker_processes auto и worker_connections в несколько тысяч закрывают
почти все случаи.
Грубая оценка числа одновременных проксируемых запросов -
worker_processes × worker_connections ÷ 2: одно соединение с клиентом,
другое с бэкендом. Это не гарантированный предел клиентов: часть слотов
занимают другие соединения, а файловые дескрипторы ограничены отдельно.
Числа трогают после метрики, а не до. Если active connections болтается на
сотне при потолке в шестнадцать тысяч, тюнинг этих строк не даст ничего, и
проблема где-то ещё.
Разбор: считаем потолок целиком
Возьмём боевой случай: nginx проксирует магазин, worker_processes auto на
8-ядерной машине, worker_connections 4096.
слотов всего: 8 × 4096 = 32 768
на клиента нужно: 2 (клиент + бэкенд)
потолок клиентов: 32 768 ÷ 2 = 16 384
Теперь второе ограничение. Каждое соединение - это открытый файловый дескриптор, а их число ограничено на процесс:
cat /proc/$(pgrep -f 'nginx: worker' | head -1)/limits | grep 'open files'
Max open files 1024 1024 files
Тысяча двадцать четыре против четырёх тысяч заявленных. То есть настоящий
потолок здесь - не 4096 на воркер, а 1024, и при подходе к нему в error_log
появится:
worker_connections exceed open file resource limit: 1024
Лимит живёт в двух местах, и важно понимать, какое из них главнее:
worker_rlimit_nofile 8192;
worker_rlimit_nofile задаёт лимит РАБОЧИМ процессам, и поднять его можно в
пределах ЖЁСТКОГО лимита, с которым запущен главный процесс. Жёсткий лимит
выдаёт systemd (LimitNOFILE в юните), и у современных систем он большой: на
Ubuntu 22.04 с systemd 249 systemctl show -p DefaultLimitNOFILE печатает
524288, а мягкий - 1024. То есть в типичном случае ОДНОЙ директивы в
nginx.conf достаточно, юнит править не нужно.
Юнит нужен тогда, когда хочется выйти за жёсткий лимит - или когда дистрибутив
выставил его низким. Замер на стенде показывает оба случая: контейнер запущен с
лимитами 1024 мягкий и 4096 жёсткий.
без worker_rlimit_nofile soft=1024 hard=4096
worker_rlimit_nofile 3000, reload soft=3000 hard=3000 ← reload применил
worker_rlimit_nofile 9000, reload soft=1024 hard=4096 ← откат, см. лог
[alert] setrlimit(RLIMIT_NOFILE, 9000) failed (1: Operation not permitted)
Отсюда два вывода, которые стоит запомнить дословно. Первый: worker_rlimit_nofile
применяется ПО RELOAD - новые рабочие процессы получают новый лимит, перезапуск
службы не нужен. Второй: выше жёсткого лимита попросить нельзя - setrlimit
отказывает, и рабочий процесс молча остаётся со старым значением. Ошибка видна
только в error_log, и только с уровнем alert.
Что такое юнит systemd
Юнит systemd - текстовый файл, описывающий службу: чем её запускать, когда
перезапускать и какие лимиты ядра ей выставить. Юнит nginx лежит в
/lib/systemd/system/nginx.service, но править его руками не надо -
обновление пакета перезапишет файл. Свои строки кладут отдельным довеском:
sudo systemctl edit nginx
Команда открывает пустой файл /etc/systemd/system/nginx.service.d/override.conf,
куда и пишется добавка:
[Service]
LimitNOFILE=8192
После правки нужен sudo systemctl daemon-reload и полный
sudo systemctl restart nginx: ЖЁСТКИЙ лимит выдаётся процессу при старте, и
reload его не поднимает. А вот worker_rlimit_nofile внутри этого жёсткого
лимита reload применяет - это проверено замером выше.
Разбор: где формула врёт
Из первого разбора легко унести «auto всегда правильно». Вот случай, где auto даёт заведомо плохое число.
nginx запущен в контейнере с ограничением в половину ядра на машине с 64
ядрами. auto считает ядра хоста, потому что видит их в /proc/cpuinfo:
worker_processes auto → 64 воркера
доступно процессорного времени → 0,5 ядра
Шестьдесят четыре процесса делят половину ядра. Каждый со своим набором соединений, каждому нужна память под буферы, и все они переключаются друг с другом. Итог хуже, чем от одного воркера.
Проверить, что видит контейнер, можно прямо из него:
docker exec lab nproc
docker exec lab cat /sys/fs/cgroup/cpu.max
Если nproc показывает 64, а cpu.max говорит 50000 100000 (то есть
полъядра), число воркеров надо ставить руками. Сам nginx квоту НЕ СМОТРИТ -
проверено замером на 1.31.6:
nproc внутри контейнера: 4
/sys/fs/cgroup/cpu.max: 200000 100000 (квота = 2 ядра)
воркеров при worker_processes auto: 4 ← квота не учтена
auto считает число ONLINE-процессоров, и всё. Это открытый запрос в
репозитории nginx, а не особенность сборки.
А вот у ОФИЦИАЛЬНОГО образа Docker есть свой автотюнинг - отдельный скрипт
/docker-entrypoint.d/30-tune-worker-processes.sh, который читает и cpuset, и
квоту (и v1, и v2), берёт минимум и переписывает директиву в nginx.conf.
Включается он переменной окружения:
docker run -e NGINX_ENTRYPOINT_WORKER_PROCESSES_AUTOTUNE=1 --cpus=2 nginx:1.31.6-alpine
без переменной: worker_processes auto; → 4 воркера при --cpus=2
с переменной: worker_processes 2; → 2 воркера
Разницу стоит держать в голове: это поведение ОБРАЗА, а не nginx. На своей сборке, в пакете из репозитория дистрибутива и в чужом образе этого скрипта нет, и число воркеров под квотой задаётся руками.
Как понять, что упёрся
Включи страницу статуса:
location = /nginx_status {
stub_status;
allow 127.0.0.1;
deny all;
}
curl -s http://127.0.0.1/nginx_status
Active connections: 291
server accepts handled requests
16630948 16630948 31070465
Reading: 6 Writing: 179 Waiting: 106
Смотреть надо на две вещи. Active connections против потолка - если
подобралось, пора поднимать. И второе число в средней строке (handled)
против первого (accepts): если handled меньше, значит соединения
отбрасывались, и это и есть упёршийся потолок.
Что ломается без этого
worker_connections are not enough в error_log - слоты кончились.
Клиенты в этот момент получают отказ или таймаут.
too many open files - кончились дескрипторы. То же самое, но упёрлось
раньше, в системный лимит.
«В контейнере nginx медленнее, чем на голой машине» - почти всегда
worker_processes auto, насчитавший ядра хоста.
Зачем это в работе
Первое - разбор отказов под нагрузкой. Когда сайт начинает отбрасывать
соединения, stub_status за секунду отвечает, упёрлись слоты или нет, и это
разводит две совершенно разные ветки расследования: настройки nginx против
проблем бэкенда.
Второе - переезд в контейнеры. Конфиг, годами работавший на железе, в
контейнере с квотой ведёт себя иначе, и worker_processes auto - первое, что
стоит проверить, а не последнее.
Теперь сам
nginx проксирует API. worker_processes auto на 4 ядрах, worker_connections
2048, worker_rlimit_nofile не задан, системный лимит 1024. Сколько
одновременных клиентов реально выдержит сервер?
Ответ. Заявленный потолок - 4 × 2048 = 8192 слота, то есть 4096 клиентов
в режиме прокси. Но каждый воркер упрётся в 1024 дескриптора раньше, чем в 2048
слотов, поэтому реально это 4 × 1024 = 4096 слотов и около двух тысяч
клиентов. Причём задолго до этого числа в error_log посыплется
worker_connections exceed open file resource limit. Чинится поднятием
worker_rlimit_nofile - и reload этого достаточно, пока запрошенное число не
выше жёсткого лимита главного процесса. Если выше (setrlimit ... failed в
error_log) - сначала LimitNOFILE в юните и полный перезапуск.
Главное
Ёмкость nginx ограничивают и worker_connections, и доступные файловые дескрипторы. Проксирование обычно требует соединения с клиентом и соединения с бэкендом. Прежде чем увеличивать настройки, найди достигнутый предел в метриках и логах; затем проверь жёсткий лимит процесса, запущенного systemd.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий