← Back to notes

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


Теория

Master / Worker

master-процесс:

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

worker-процессы:

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

Дефолт worker_processes 1, рекомендуемое значение — auto.


Single-threaded worker

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


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.


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

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

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

По шагам:

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

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

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

Где возможен 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).

Признаки: рост latency p95/p99, падение RPS, рост таймаутов и 5xx.


Практика

1. Количество воркеров

ps aux | grep nginx

2. Event loop через strace

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

3. Параметры NGINX

worker_processes auto;

events {
    worker_connections 4096;
    use epoll;
}

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

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: процессная модель и Event Loop | Aleksandr Suprun