← Back to blog

NGINX: процессная модель и Event Loop

⚙️ Теория

🧩 Master / Worker model

master-процесс:

  • читает конфигурацию;
  • открывает listening sockets;
  • запускает и контролирует worker-процессы;
  • выполняет reload, graceful shutdown, log reopen.

worker-процессы:

  • принимают подключения;
  • обрабатывают HTTP-запросы;
  • работают независимо друг от друга.

🧠 Single-threaded worker

Каждый worker в классической модели NGINX однопоточный: один процесс = один event loop.
NGINX не создаёт поток на каждое соединение, поэтому не платит цену за постоянные переключения контекста между тысячами потоков.


🔌 epoll / kqueue

NGINX использует событийные механизмы ядра:

  • epoll на Linux;
  • kqueue на BSD/macOS.

Вместо ожидания каждого сокета отдельно worker получает от ядра только готовые события: "можно читать" / "можно писать".


🚦 accept mutex

accept_mutex снижает эффект thundering herd (когда много воркеров одновременно пытаются принять одно и то же входящее соединение).
Механизм сериализует accept() между воркерами и уменьшает лишнюю конкуренцию за listening socket.

Важно по документации:

  • по умолчанию accept_mutex off;
  • на Linux с EPOLLEXCLUSIVE (ядро >= 4.5) включать его обычно не нужно;
  • при reuseport он также не требуется.

♻️ reuseport

reuseport даёт каждому worker собственный listening socket на одном порту.
Распределение входящих соединений делает ядро, что обычно уменьшает lock-contention на этапе accept.


🎯 Фокус

NGINX не масштабируется потоками — он масштабируется процессами.


Что нужно уметь объяснить

Почему один worker может держать тысячи соединений?

Коротко: у worker нет модели «один поток = одно соединение».
Один процесс ведёт много сокетов сразу и работает только с теми, которые уже готовы к чтению/записи.

Как это происходит по шагам:

  1. worker вызывает epoll_wait() и "засыпает".
  2. Ядро будит его только когда есть готовые события.
  3. worker быстро обрабатывает событие (прочитал/отправил часть данных).
  4. Переходит к следующему готовому сокету.
  5. Возвращается в epoll_wait().

Итог: тысячи соединений возможны, потому что нет тысяч потоков и нет ожидания каждого клиента по отдельности.

Почему worker не блокируется?

  • сокеты открыты в неблокирующем режиме;
  • если данные пока не готовы (EAGAIN/EWOULDBLOCK), worker не ждёт, а идёт дальше;
  • ожидание готовности делает ядро через epoll_wait() / kevent(), а не сам worker.

То есть worker тратит CPU на обработку событий, а не на "пассивное ожидание".

Где возможен contention?

Основные точки конкуренции:

  • accept() на одном listening socket (смягчают accept_mutex и reuseport);
  • общие структуры в shared memory (limit_req_zone, keys_zone, upstream zone);
  • CPU (слишком мало worker_processes, long requests, перегруженный upstream);
  • диск и сеть (очереди I/O, packet drops, медленные ответы backend).

Признаки contention:

  • растут latency p95/p99;
  • падает requests/sec;
  • увеличиваются timeouts и 5xx.

🧪 Практика

1. Посмотреть количество воркеров

ps aux | grep nginx

2. Проверить event loop через strace

strace -p <pid_worker>

Полезнее сузить до ключевых syscalls:

strace -f -e trace=epoll_wait,accept4,recvfrom,sendto -p <pid_worker>

3. Изменить параметры NGINX

В nginx.conf:

worker_processes auto;

events {
    worker_connections 4096;
    use epoll;
}

worker_connections — это максимум всех соединений на worker (клиентских, upstream и т.д.), а не только входящих клиентов.

Применить изменения:

nginx -t && sudo nginx -s reload

4. Нагрузить сервер и сравнить поведение

ab -n 50000 -c 500 http://127.0.0.1/

или

wrk -t4 -c400 -d30s http://127.0.0.1/

Сравнивайте до/после изменения:

  • latency (p50/p95/p99);
  • requests/sec;
  • ошибки (5xx, timeouts);
  • загрузку CPU по воркерам.

🧾 Вывод

Сила NGINX в том, что он масштабируется процессами и событийной моделью, а не потоками на каждое соединение.
worker остаётся эффективным под высокой нагрузкой, пока он быстро обрабатывает готовые события и не упирается в contention (accept, shared memory, CPU, I/O).
Главная практическая задача инженера: правильно подобрать worker_processes/worker_connections и проверять поведение под реальной нагрузкой, а не по теории.


📚 Ссылки


Проверка источников

NGINX: процессная модель и Event Loop | Aleksandr Suprun