⚙️ Теория
🔄 Connection Lifecycle в NGINX
Базовая цепочка обработки:
accept → read → create request → run phases → content → write → keepalive/close
По шагам:
accept
worker принимает TCP-соединение на listening socket.read
читает старт запроса и заголовки из сокета клиента.create request
создаёт внутренний объектrequestи связывает его сconnection.run phases
выполняет цепочку фаз HTTP (rewrite, access, try_files, proxy и т.д.).content
формирует ответ локально или идёт в upstream.write
отправляет ответ клиенту (частями, неблокирующе).keepalive/close
либо оставляет соединение открытым для следующего запроса, либо закрывает.
🧩 Разница: connection, request, subrequest
connection:
- TCP-соединение (сокет, состояние keepalive, таймауты);
- может жить дольше одного HTTP-запроса.
request:
- один HTTP-запрос внутри соединения;
- у HTTP/1.1 обычно по одному за раз на connection (практически без pipelining);
- завершается после отправки ответа.
subrequest:
- внутренний дочерний запрос NGINX (например
auth_request,SSI,mirror); - не отдельное клиентское соединение;
- нужен для внутренней логики до финального ответа основного request.
❓ Что нужно уметь объяснить
Почему keepalive уменьшает CPU?
- меньше новых
accept()и TCP/TLS handshakes; - меньше системных вызовов на открытие/закрытие соединений;
- меньше затрат ядра на выделение/очистку socket state;
- больше полезной работы на запрос, меньше служебных операций.
Итог: при том же трафике CPU обычно ниже, а latency стабильнее.
Что происходит при медленном клиенте?
- NGINX готовит ответ быстро, но отправляет его медленно (slow receiver);
- сокет часто «не готов к записи», данные дольше висят в буферах;
- connection дольше занят, растут
active connectionsи память на буферы; - при большом числе таких клиентов можно упереться в
worker_connections(лимит учитывает все типы соединений на worker).
Что происходит при медленном upstream?
- клиент пришёл быстро, но backend отвечает медленно;
- request дольше остаётся "в полёте", растёт очередь активных запросов;
- увеличиваются latency, возможны
upstream timed outи 502/504; - воркеры не блокируются как поток, но держат больше незавершённых состояний.
🧪 Практика
1. Отключить keepalive
В server или http:
keepalive_timeout 0;
keepalive_timeout 0 отключает keep-alive для клиентских соединений.
Перезагрузить:
nginx -t && sudo nginx -s reload
2. Сравнить RPS и CPU
До/после изменения keepalive:
wrk -t4 -c200 -d30s http://127.0.0.1/
Параллельно смотреть CPU:
top -H -p $(pgrep -d',' -f 'nginx: worker process')
Сравнивать:
- requests/sec;
- latency p95/p99;
- загрузку CPU по worker.
3. Сделать upstream с искусственной задержкой
Поднять простой sleep backend на 127.0.0.1:18080:
python3 -c '
from http.server import BaseHTTPRequestHandler, HTTPServer
import time
class H(BaseHTTPRequestHandler):
def do_GET(self):
time.sleep(1) # искусственная задержка backend
body = b"slow backend\n"
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
HTTPServer(("127.0.0.1", 18080), H).serve_forever()
'
Для NGINX:
upstream slow_backend {
server 127.0.0.1:18080;
}
location /slow {
proxy_pass http://slow_backend;
proxy_read_timeout 5s;
}
Затем нагрузка:
wrk -t4 -c200 -d30s http://127.0.0.1/slow
4. Посмотреть сводку сокетов
ss -s
Полезно сравнить до/после:
TCP: inuse;estab;timewait;orphaned.
🧾 Вывод
connection lifecycle помогает понять, где именно теряется производительность: на установке соединений, на клиентской стороне или в upstream.
keepalive экономит CPU за счёт повторного использования соединений, но медленные клиенты и медленные backend всё равно увеличивают время жизни запросов и нагрузку на воркеры.
Практически важно не только измерять RPS, но и одновременно смотреть latency, CPU и состояние TCP (ss -s) — только так видно реальную причину деградации.
📚 Ссылки
- NGINX docs: How nginx processes a request
- NGINX docs: keepalive_timeout
- NGINX docs: proxy_read_timeout
- man 8 ss