nginx: Tuning performansi
Ciljevi lekcije
Nakon ove lekcije trebalo bi da:
- izmeriš stanje pre nego što bilo šta menjaš
- razumeš odnos
worker_processes,worker_connectionsi sistemskih limita - podesiš bafere tako da se telo zahteva ne prelива na disk
- prepoznaš kada je usko grlo u nginx-u, a kada — mnogo češće — negde drugde
- znaš koja sysctl podešavanja imaju smisla, a koja se prepisuju bez razumevanja
1. Prvo pravilo: izmeri
Najveći deo saveta o tuningu koji kruži internetom je prepisivanje bez konteksta. Pre nego što promeniš ijedan red, treba da znaš tri stvari:
Šta je trenutno stanje. Bez polazne mere ne možeš da tvrdiš da si nešto poboljšao.
Gde je usko grlo. U ogromnoj većini slučajeva nije u nginx-u. nginx na skromnom hardveru servira desetine hiljada statičkih zahteva u sekundi bez ikakvog podešavanja. Ako ti je sajt spor, uzrok je najverovatnije aplikacija, upit u bazu ili disk.
Šta zapravo pokušavaš da postigneš. Veći broj zahteva u sekundi? Manje kašnjenje? Više istovremenih konekcija? To su različiti ciljevi sa različitim rešenjima.
stub_status — osnovni uvid
server {
listen 127.0.0.1:8080;
location = /nginx_status {
stub_status;
allow 127.0.0.1;
deny all;
access_log off;
}
}
curl http://127.0.0.1:8080/nginx_status
Active connections: 43
server accepts handled requests
12847 12847 41203
Reading: 0 Writing: 3 Waiting: 40
Kako se ovo čita:
- Active connections — trenutno otvorene konekcije
- accepts / handled — ako se ovi brojevi razlikuju, konekcije su odbijane; to je znak da si udario u limit
- requests — ukupno zahteva; odnos prema
handledpokazuje koliko zahteva ide po konekciji (keep-alive efikasnost) - Reading — čita se zahtev od klijenta
- Writing — šalje se odgovor
- Waiting — otvorene keep-alive konekcije bez aktivnosti; velik broj ovde je normalan i jeftin
Merenje opterećenja
sudo apt install apache2-utils # ab
ab -n 5000 -c 100 -k http://localhost/
-k uključuje keep-alive, što odgovara ponašanju browsera. Bez toga meriš uspostavljanje konekcija, ne serviranje.
Za ozbiljnije merenje wrk daje bolju sliku kašnjenja:
sudo apt install wrk
wrk -t4 -c100 -d30s --latency http://localhost/
Testiraj sa druge mašine kada god možeš. Alat za opterećenje koji radi na istom serveru takmiči se sa nginx-om za procesor i rezultat je neupotrebljiv.
Gledaj percentile, ne prosek. Prosečno vreme odgovora od 50 ms uz p99 od 4 sekunde znači da svaki stoti korisnik čeka četiri sekunde. Prosek to skriva.
2. Worker procesi i konekcije
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 4096;
multi_accept on;
}
worker_processes
auto znači „koliko ima jezgara" i to je gotovo uvek tačan izbor. Više workera od jezgara ne donosi ništa jer su procesi ionako zauzeti; manje znači da ne koristiš mašinu.
Izuzetak je kontejner sa ograničenim CPU udelom — auto tamo vidi sva jezgra domaćina, a ne dodeljeni deo. Tada broj postavi ručno.
worker_connections
Maksimalan broj konekcija po worker procesu. Ubuntu podrazumeva 768, što je konzervativno.
teorijski maksimum = worker_processes × worker_connections
Za četiri jezgra i 4096 konekcija to je 16.384. Ali:
Kod reverse proxy postavke svaki zahtev troši dve konekcije — jednu ka klijentu, drugu ka backend-u. Realan kapacitet je polovina.
Konekcije se troše i na drugo: DNS upite, otvorene fajlove u nekim slučajevima. Ostavi rezervu.
Kada udariš u limit, u error logu se pojavi:
worker_connections are not enough
To je jasan signal da treba podići. Bez te poruke, podizanje brojke ništa ne rešava.
worker_rlimit_nofile — limit koji se najčešće zaboravi
Svaka konekcija je file deskriptor. Ako je sistemski limit niži od worker_connections, dobićeš:
socket() failed (24: Too many open files)
worker_rlimit_nofile podiže limit za worker procese i treba da bude bar dvostruko veći od worker_connections, jer se deskriptori troše i na fajlove, socket-e ka backend-u i log fajlove.
Ali ni to nije dovoljno samo po sebi — systemd nameće svoj limit. Proveri stvarno stanje:
WPID=$(pgrep -f "nginx: worker" | head -1)
sudo cat /proc/$WPID/limits | grep "open files"
Ako je vrednost niža nego što očekuješ, podigni je u systemd unitu:
sudo systemctl edit nginx
[Service]
LimitNOFILE=131072
sudo systemctl daemon-reload
sudo systemctl restart nginx
Restart, ne reload — menja se unit.
multi_accept
Kada je uključeno, worker prihvata sve konekcije koje čekaju odjednom, umesto jedne po pozivu. Pod velikim opterećenjem smanjuje broj sistemskih poziva.
Pod malim opterećenjem razlika je nemerljiva. Uključi ako imaš veliki priliv novih konekcija; inače ostavi kako jeste.
accept_mutex
Stariji vodiči raspravljaju o ovome. Od nginx-a 1.11.3 podrazumevano je isključeno i to je ispravno na Linuxu sa EPOLLEXCLUSIVE podrškom. Ne diraj.
3. Serviranje fajlova
http {
sendfile on;
sendfile_max_chunk 2m;
tcp_nopush on;
tcp_nodelay on;
open_file_cache max=20000 inactive=60s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
}
sendfile on prebacuje kopiranje fajla u socket u kernel — podaci ne prolaze kroz nginx-ov adresni prostor. Ubuntu ovo već uključuje.
sendfile_max_chunk 2m je važan detalj koji se retko pominje. Bez njega, jedan zahtev za veliki fajl može da zadrži worker u jednom sendfile() pozivu i uspori sve ostale konekcije tog workera. Ograničavanje na komade od 2 MB daje ostalima priliku.
tcp_nopush on ima smisla samo uz sendfile — nagoni kernel da šalje pune pakete umesto da odmah gura zaglavlja.
open_file_cache pamti deskriptore i rezultate stat() poziva. Za sajt sa mnogo malih fajlova ovo je merljiva ušteda. open_file_cache_errors on kešira i „fajl ne postoji", što štedi disk kod botova koji lupaju nepostojeće putanje.
Na razvojnoj mašini isključi ovo, inače ne vidiš svoje izmene do isteka valid perioda.
Veliki fajlovi
Za serviranje velikih fajlova (video, arhive), sendfile može da blokira worker ako podatak nije u kešu stranica:
location /video/ {
aio threads;
directio 4m;
output_buffers 2 1m;
sendfile off;
}
aio threads prebacuje diskovne operacije u pomoćne niti (mehanizam iz lekcije 1), a directio zaobilazi keš stranica za fajlove preko 4 MB — nema smisla trošiti RAM na keširanje fajla koji se streamuje jednom.
sendfile i directio se međusobno isključuju, pa ga ovde gasiš.
4. Keep-alive
http {
keepalive_timeout 65s;
keepalive_requests 1000;
}
keepalive_requests je podrazumevano 1000. Na HTTP/2, gde jedna konekcija nosi mnogo više zahteva, ovo je često prenisko:
keepalive_requests 10000;
keepalive_timeout od 65 sekundi je razuman kompromis. Duže drži konekcije otvorenim bez koristi; kraće tera klijente na ponovno uspostavljanje.
Podsetnik iz lekcije 8 — keep-alive prema backend-u je zaseban mehanizam i traži sva tri reda:
upstream backend {
server 127.0.0.1:3000;
keepalive 32;
}
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
Ovo je često najveći pojedinačni dobitak u proxy postavkama, veći od svega ostalog u ovoj lekciji.
5. Baferi
Bafer koji je premali znači da nginx piše na disk. Bafer koji je prevelik troši memoriju po konekciji.
http {
client_body_buffer_size 16k;
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
proxy_buffer_size 8k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;
}
Kako znaš da je premalo
Telo zahteva se prelива na disk — u error logu:
a client request body is buffered to a temporary file
Svaki takav zapis je nepotreban upis na disk. Ako se pojavljuje često, podigni client_body_buffer_size na veličinu tipičnog POST zahteva. Za formu sa nekoliko polja 16k je dosta; za API koji prima veće JSON dokumente možda 128k.
Ne diži bez potrebe — bafer se alocira po konekciji. Sa 128k i 5000 istovremenih konekcija to je 640 MB.
Zaglavlja odgovora ne staju:
upstream sent too big header while reading response header from upstream
Podigni proxy_buffer_size. Tipičan uzrok su aplikacije koje šalju velike kolačiće ili mnogo zaglavlja.
Zaglavlja zahteva ne staju — klijent dobija 400 ili 494. Podigni large_client_header_buffers.
Merenje umesto pogađanja
sudo grep -c "buffered to a temporary file" /var/log/nginx/error.log
sudo grep -c "too big header" /var/log/nginx/error.log
Ako su nule, tvoji baferi su u redu i nema šta da se dira.
6. TLS performanse
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_buffer_size 4k;
ssl_session_cache omogućava ponovno korišćenje TLS sesije bez punog rukovanja. Zona od 1 MB drži oko 4000 sesija. Ovo je najveći TLS dobitak koji možeš dobiti jednim redom.
ssl_buffer_size 4k smanjuje kašnjenje prvog bajta za male odgovore. Podrazumevanih 16k je bolje za velike prenose. Za obične sajtove 4k je bolji izbor.
ECDSA sertifikati su osetno brži od RSA pri rukovanju. Let's Encrypt ih izdaje:
sudo certbot certonly --nginx --key-type ecdsa -d example.com
Možeš ponuditi i oba istovremeno — nginx bira prema tome šta klijent podržava:
ssl_certificate /etc/letsencrypt/live/example.com-ecdsa/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com-ecdsa/privkey.pem;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
Podsetnik iz lekcije 9: ssl_session_tickets off je preporuka iz sigurnosnih razloga, i jeste kompromis prema performansama. Ako ih uključiš, rotiraj ključ.
7. reuseport
listen 443 ssl reuseport;
Svaki worker dobija sopstveni socket i kernel raspoređuje dolazne konekcije među njima. Uklanja takmičenje između workera pri velikom priliv novih konekcija.
Sme da stoji samo u jednom server bloku po adresi i portu. U više blokova nginx neće startovati.
Korist je merljiva tek pri desetinama hiljada novih konekcija u sekundi. Za većinu sajtova nema efekta.
8. Sysctl — šta ima smisla
Ovde je najviše prepisivanja bez razumevanja. Nekoliko podešavanja koja stvarno imaju veze sa nginx-om:
sudo tee /etc/sysctl.d/99-nginx.conf > /dev/null <<'EOF'
# Red čekanja za nove konekcije
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
# Opseg lokalnih portova (bitno za mnogo konekcija ka backend-u)
net.ipv4.ip_local_port_range = 1024 65535
# Ponovna upotreba socket-a u TIME_WAIT stanju
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
# Red na mrežnom interfejsu
net.core.netdev_max_backlog = 16384
# Ukupno file deskriptora na sistemu
fs.file-max = 2097152
EOF
sudo sysctl --system
Uz somaxconn ide i nginx strana:
listen 443 ssl backlog=65535;
Bez toga nginx koristi svoj podrazumevani backlog (511 na Linuxu) i sistemska vrednost ostaje neiskorišćena.
Šta se najčešće prepisuje bez potrebe
net.ipv4.tcp_tw_recycle — uklonjen iz kernela i lomio je konekcije iza NAT-a. Ako ga vidiš u nekom vodiču, taj vodič je zastareo.
Podešavanja TCP prozora i congestion control algoritama — moderni kernel to radi bolje sam osim u vrlo specifičnim slučajevima.
vm.swappiness, vm.overcommit_memory — nemaju veze sa nginx-om.
Pravilo: ako ne umeš da objasniš šta parametar radi i koji problem rešava, ne stavljaj ga.
Provera stvarnog stanja
# Da li ima odbačenih konekcija zbog punog reda
netstat -s | grep -iE "listen|overflow"
# Koliko socket-a je u TIME_WAIT
ss -tan state time-wait | wc -l
# Koliko deskriptora sistem koristi
cat /proc/sys/fs/file-nr
9. Kada nginx nije problem
Pre nego što nastaviš da podešavaš nginx, isključi ove mogućnosti:
Backend je spor. Iz loga (lekcija 13) uporedi $request_time i $upstream_response_time. Ako je backend gotovo ceo request_time, nginx nema šta da popravi.
Disk je usko grlo:
iostat -x 2 5 # sudo apt install sysstat
Kolona %util blizu 100 znači da je disk zasićen.
Procesor je zasićen — proveri ko ga troši:
top -b -n1 | head -20
Ako su to PHP-FPM ili aplikacioni procesi, podešavaj njih.
Mreža je usko grlo:
sar -n DEV 2 5
DNS je spor — ako proxy_pass koristi imena hostova sa promenljivom, svaki zahtev čeka razrešenje. Podesi resolver ... valid= ili koristi IP adrese.
10. Metodologija
- Izmeri polazno stanje i zabeleži brojke
- Formuliši hipotezu — „mislim da je usko grlo X"
- Promeni jednu stvar
- Izmeri ponovo pod istim uslovima
- Zadrži promenu ako je pomogla, vrati je ako nije
- Ponovi
Menjanje deset stvari odjednom i konstatacija da je „sada brže" nije tuning nego pogađanje. Kad se posle dva meseca pojavi problem, nećeš znati koja od tih deset ga je uzrokovala.
Vodi zapisnik izmena. Komentar u konfiguraciji sa datumom i razlogom vredi više nego što deluje:
# 2026-03-14: podignuto sa 768 zbog "worker_connections are not enough"
# pri naletu od ~3000 istovremenih korisnika
worker_connections 4096;
Praktična vežba
1. Uključi stub_status i posmatraj ga pod opterećenjem. U jednom terminalu:
watch -n1 'curl -s http://127.0.0.1:8080/nginx_status'
U drugom pokreni ab -n 20000 -c 200 -k http://localhost/ i posmatraj kako se menjaju Active, Reading, Writing, Waiting.
2. Izmeri polazno stanje i zapiši:
wrk -t4 -c100 -d30s --latency http://localhost/ | tee /tmp/polazno.txt
3. Izmeri efekat keep-alive konekcija ka backend-u. Postavi proxy ka Node aplikaciji iz lekcije 7, izmeri bez keep-alive, pa dodaj sva tri reda i izmeri ponovo:
wrk -t4 -c100 -d20s http://localhost/ # pre
# dodaj keepalive 32; proxy_http_version 1.1; proxy_set_header Connection "";
wrk -t4 -c100 -d20s http://localhost/ # posle
ss -tan state time-wait | wc -l
Ovo je najveća razlika koju ćeš izmeriti u celoj lekciji.
4. Proveri stvarni limit deskriptora:
WPID=$(pgrep -f "nginx: worker" | head -1)
sudo cat /proc/$WPID/limits | grep "open files"
ulimit -n
Podigni ga kroz systemd drop-in i proveri da se promenilo nakon restarta.
5. Izazovi prelivanje bafera na disk:
dd if=/dev/zero of=/tmp/telo.bin bs=1k count=200
curl -s -o /dev/null -X POST --data-binary @/tmp/telo.bin \
-H "Host: test.local" http://localhost/
sudo grep "buffered to a temporary file" /var/log/nginx/error.log | tail -2
Podigni client_body_buffer_size na 256k i uveri se da poruka nestaje.
6. Uporedi sendfile on i off na velikom fajlu:
sudo dd if=/dev/urandom of=/var/www/test/html/veliki.bin bs=1M count=200
for s in on off; do
sudo sed -i "s/sendfile .*/sendfile $s;/" /etc/nginx/nginx.conf
sudo nginx -t -q && sudo systemctl reload nginx
echo -n "sendfile $s: "
curl -s -o /dev/null -w "%{speed_download} B/s\n" \
-H "Host: test.local" http://localhost/veliki.bin
done
7. Uporedi TLS sa i bez sesijskog keša. Postavi ssl_session_cache off;, izmeri, pa vrati shared:SSL:50m i izmeri ponovo:
ab -n 1000 -c 50 https://localhost/
Bez -k, da bi svaki zahtev radio rukovanje.
8. Prođi kroz proveru da nginx nije problem — pokreni iostat, top i uporedi $request_time sa $upstream_response_time u svom logu.
Rezime
- Izmeri pre i posle; menjaj jednu stvar odjednom; gledaj percentile, ne prosek.
- U ogromnoj većini slučajeva usko grlo nije nginx nego aplikacija, baza ili disk.
worker_processes autoje skoro uvek tačno; izuzetak su kontejneri sa ograničenim CPU udelom.worker_connectionspodigni tek kada vidišworker_connections are not enoughu logu.worker_rlimit_nofilenije dovoljan sam — systemdLimitNOFILEmora da ga prati.- Kod reverse proxy-ja jedan zahtev troši dve konekcije.
sendfile_max_chunk 2msprečava da jedan veliki fajl zadrži worker.- Keep-alive prema backend-u je obično najveći pojedinačni dobitak u proxy postavkama.
- Bafere podižeš kada vidiš konkretnu poruku u error logu, ne preventivno — alociraju se po konekciji.
ssl_session_cacheje najjeftiniji TLS dobitak; ECDSA sertifikati su brži od RSA.- Uz
net.core.somaxconnide ibacklog=ulistendirektivi, inače ostaje neiskorišćen. tcp_tw_recycleviše ne postoji — vodič koji ga pominje je zastareo.- Komentariši izmene sa datumom i razlogom.
Pitanja za proveru
- Postavio si
worker_connections 8192i dobijašToo many open files. Šta si propustio? - Zašto je kod reverse proxy postavke realan kapacitet polovina teorijskog?
- Šta znači poruka
a client request body is buffered to a temporary filei kada je treba ignorisati? - Zašto ne treba postaviti
client_body_buffer_size 1m„za svaki slučaj"? - Čemu služi
sendfile_max_chunk? $request_timeje 2 sekunde,$upstream_response_timeje 1.95 sekundi. Ima li smisla podešavati nginx?- Zašto
net.core.somaxconn = 65535sam po sebi ništa ne menja? - Zašto
reuseportsme da stoji samo u jednomlistenredu po portu?
Sledeća lekcija: stream modul — nginx kao TCP i UDP proxy, ispred baze podataka, SMTP servera ili bilo čega što ne govori HTTP.
Comments
Post a Comment