⚙️ Теория
🔄 Upstream lifecycle
Упрощённый путь проксированного запроса:
connect → send → read → buffer → send downstream
По шагам:
connect
NGINX открывает соединение к backend.send
отправляет upstream-запрос (headers/body).read
читает ответ backend.buffer
временно хранит данные в буферах/файлах (если включена буферизация).send downstream
отправляет ответ клиенту с учётом скорости клиента.
📦 buffering
proxy_buffering on:
- NGINX старается быстрее дочитать ответ upstream в свои буферы;
- backend освобождается раньше;
- медленный клиент меньше влияет на backend.
proxy_buffering off:
- ответ ближе к stream-передаче клиенту;
- меньше внутренней буферизации;
- но медленный клиент сильнее “удерживает” request в работе.
🚦 backpressure
Backpressure появляется, когда следующий этап медленнее предыдущего:
- backend отвечает быстрее, чем клиент читает;
- или клиентов слишком много, и NGINX/ядро не успевают отправлять данные.
Тогда растут очереди, время жизни request и латентность.
🐢 slow upstream
Медленный backend увеличивает длительность каждого запроса.
При фиксированном числе connection это снижает throughput: меньше запросов завершается в единицу времени.
🔁 retry
NGINX может повторить запрос на другой upstream при ошибках/таймаутах (в пределах настроек):
proxy_next_upstreamproxy_next_upstream_triesproxy_next_upstream_timeout
По документации retry возможен только если клиенту ещё ничего не отправлено;
для неидемпотентных методов (POST, LOCK, PATCH) повтор отключён по умолчанию и включается отдельно (non_idempotent).
Retry повышает устойчивость, но при перегрузе может усилить нагрузку на backend.
❓ Что нужно уметь объяснить
Почему медленный upstream может “убить” throughput?
Если backend отвечает дольше, каждый request занимает worker-ресурсы дольше.
Одновременно растёт число незавершённых запросов, а завершённых в секунду становится меньше.
Когда в полёте слишком много запросов, система уходит в очереди и tail latency (p95/p99) резко растёт.
Что происходит при заполнении worker_connections?
- worker перестаёт принимать новые клиентские соединения;
- также может отказываться от новых upstream-соединений;
- в
stub_statusметрикаhandledперестаёт догонятьaccepts; - даже при свободном CPU сервис деградирует из-за лимита соединений/FD.
То есть это hard-limit на параллелизм одного worker.
Как proxy_buffering влияет на память?
on: больше памяти (и иногда temp files), зато upstream освобождается быстрее;off: меньше внутренней буферизации, но request дольше живёт при медленных клиентах.
Компромисс: память vs устойчивость к slow clients.
🧪 Практика
1. Создать backend с 500ms delay
python3 -c '
from http.server import BaseHTTPRequestHandler, HTTPServer
import time
class H(BaseHTTPRequestHandler):
def do_GET(self):
time.sleep(0.5) # 500ms delay
body = b"ok\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()
'
2. Настроить proxy + статус
upstream slow_backend {
server 127.0.0.1:18080;
keepalive 64;
}
server {
listen 8080;
server_name _;
location / {
proxy_pass http://slow_backend;
proxy_connect_timeout 1s;
proxy_read_timeout 2s;
proxy_buffering on; # потом сравнить с off
}
location /nginx_status {
stub_status;
allow 127.0.0.1;
deny all;
}
}
Применить:
nginx -t && sudo nginx -s reload
3. Подать 1000 RPS
Если установлен wrk2 (фиксированный RPS):
wrk -t8 -c400 -d30s -R1000 http://127.0.0.1:8080/
Если обычный wrk, подбирайте -t/-c, чтобы выйти примерно на 1000 RPS:
wrk -t8 -c800 -d30s http://127.0.0.1:8080/
4. Наблюдать active connections, CPU, latency
active connections:
watch -n1 "curl -s http://127.0.0.1:8080/nginx_status"
CPU worker:
top -H -p $(pgrep -d',' -f 'nginx: worker process')
Latency:
- из отчёта
wrk(Latency,Req/Sec, tail values); - дополнительно смотрите ошибки/таймауты в
access.logиerror.log.
Сравните два прогона: proxy_buffering on и proxy_buffering off.
🧾 Вывод
Деградация upstream почти всегда проявляется как рост времени жизни запроса, затем рост активных соединений и падение throughput.
proxy_buffering и retry-политика меняют распределение нагрузки между backend, NGINX и клиентами, но не убирают корневую причину медленного upstream.
Надёжный анализ строится на трёх метриках одновременно: active connections, CPU по worker и latency под фиксированным профилем нагрузки.
📚 Ссылки
- NGINX docs: proxy_buffering
- NGINX docs: proxy_next_upstream
- NGINX docs: upstream keepalive
- NGINX docs: stub_status
- NGINX core docs: worker_connections