Теория
Upstream lifecycle
connect → send → read → buffer → send downstream
connect— открывает соединение к backend.send— отправляет upstream-запрос (headers/body).read— читает ответ backend.buffer— временно хранит данные в буферах/файлах (если буферизация включена).send downstream— отправляет ответ клиенту с учётом его скорости.
buffering
proxy_buffering on (дефолт):
- NGINX быстрее дочитывает ответ upstream в буферы;
- backend освобождается раньше;
- медленный клиент меньше влияет на backend.
proxy_buffering off:
- ответ ближе к stream-передаче клиенту;
- меньше внутренней буферизации;
- медленный клиент сильнее «удерживает» request.
backpressure
Появляется, когда следующий этап медленнее предыдущего:
- backend отвечает быстрее, чем клиент читает;
- или клиентов слишком много, NGINX/ядро не успевают отправлять.
Растут очереди, время жизни request, латентность.
slow upstream
Медленный backend увеличивает длительность каждого запроса. При фиксированном числе connection это снижает throughput.
retry
NGINX может повторить запрос на другой upstream при ошибках/таймаутах:
proxy_next_upstream(дефолт:error timeout);proxy_next_upstream_tries;proxy_next_upstream_timeout.
Retry возможен только если клиенту ещё ничего не отправлено. Для неидемпотентных методов (POST, LOCK, PATCH) повтор отключён по умолчанию и включается через non_idempotent.
Retry повышает устойчивость, но при перегрузе усиливает нагрузку на backend.
Что нужно уметь объяснить
Почему медленный upstream «убивает» throughput?
Каждый request занимает worker-ресурсы дольше. Растёт число незавершённых запросов, завершённых в секунду — меньше. Когда «в полёте» слишком много запросов, система уходит в очереди и tail latency (p95/p99) резко растёт.
Что происходит при заполнении worker_connections?
- worker перестаёт принимать новые клиентские соединения;
- может отказываться от новых upstream-соединений;
- в
stub_statusметрикаhandledперестаёт догонятьaccepts; - даже при свободном CPU сервис деградирует из-за лимита соединений/FD.
Hard-limit на параллелизм одного worker (дефолт worker_connections 512).
Как 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)
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:
wrk -t8 -c800 -d30s http://127.0.0.1:8080/
4. Active connections, CPU, latency
watch -n1 "curl -s http://127.0.0.1:8080/nginx_status"
top -H -p $(pgrep -d',' -f 'nginx: worker process')
Latency:
- из отчёта
wrk(Latency,Req/Sec, tail values); - ошибки/таймауты в
access.logиerror.log.
Сравнить: proxy_buffering on vs proxy_buffering off.