Ottimizzazione dei Buffer di Nginx: Configurazione di `client_body_buffer_size` e `proxy_buffer_size`
Ottimizza i buffer delle richieste e dei proxy di Nginx senza sprecare memoria o causare buffering su disco evitabile.
Ottimizzazione dei Buffer di Nginx: Configurazione di client_body_buffer_size e proxy_buffer_size
L'ottimizzazione dei buffer di Nginx non consiste nel rendere ogni buffer enorme. Si tratta di decidere quali dati devono risiedere in memoria, quali possono essere scritti su disco senza problemi e quali risposte dovrebbero essere trasmesse in streaming invece di essere completamente bufferizzate.
La parte confusa è che diverse direttive suonano simili. client_body_buffer_size si applica ai corpi delle richieste provenienti dai client. proxy_buffer_size e proxy_buffers si applicano alle risposte che arrivano da un server upstream quando Nginx agisce come proxy inverso. FastCGI ha le proprie direttive corrispondenti. Se ottimizzi il lato sbagliato, non migliorerà nulla.
Una buona sessione di ottimizzazione inizia dai sintomi:
- I log degli errori di Nginx menzionano file temporanei.
- I caricamenti o le richieste POST di grandi dimensioni sembrano lenti.
- Le risposte API proxygate si bloccano sotto carico.
- I worker utilizzano più memoria del previsto.
- Intestazioni di risposta grandi causano errori upstream.
Questi sintomi non hanno tutti la stessa soluzione. Buffer più grandi possono ridurre l'I/O su disco, ma aumentano anche la pressione sulla memoria per richiesta. Su un sito molto trafficato, un'impostazione che sembra piccola per richiesta può diventare grande quando moltiplicata per migliaia di connessioni simultanee.
Buffering del corpo della richiesta: client_body_buffer_size
client_body_buffer_size controlla il buffer utilizzato mentre Nginx legge il corpo di una richiesta del client. Pensa a invii di moduli, corpi JSON POST e caricamenti. Se il corpo della richiesta è più grande di questo buffer, Nginx può scrivere parte di esso in un file temporaneo sotto client_body_temp_path.
Un'impostazione di base è simile a questa:
http {
client_body_buffer_size 128k;
}
Questo non cambia la dimensione massima consentita per il caricamento. Questa è controllata da client_max_body_size:
server {
client_max_body_size 20m;
client_body_buffer_size 128k;
}
Queste due direttive rispondono a domande diverse. client_max_body_size dice: "Quanto grande può essere la richiesta prima che Nginx la rifiuti?" client_body_buffer_size dice: "Quanto del corpo della richiesta dovrebbe mantenere Nginx in memoria prima di utilizzare un file temporaneo?"
Per un'API JSON in cui la maggior parte dei corpi delle richieste è inferiore a 32 KB e alcuni sono 200 KB, un buffer di 128k può ridurre le scritture di file temporanei senza essere eccessivo. Per un servizio di caricamento file che accetta video da 50 MB, impostare client_body_buffer_size 50m a livello globale sarebbe un cattivo compromesso. Riservaresti troppa memoria per un carico di lavoro in cui il buffering su disco potrebbe essere accettabile.
Controlla il log degli errori per indizi come:
[warn] a client request body is buffered to a temporary file
Questo avviso non è automaticamente un fallimento. È Nginx che ti dice che un corpo ha superato il buffer in memoria. Se accade occasionalmente durante caricamenti di grandi dimensioni, va bene. Se accade costantemente per richieste API ordinarie, ottimizza il buffer o correggi i client che inviano payload grandi.
Buffering delle risposte proxy: proxy_buffer_size e proxy_buffers
Quando Nginx funge da proxy per un'applicazione upstream, il buffering delle risposte è solitamente abilitato per impostazione predefinita. Nginx legge rapidamente la risposta upstream, la memorizza nei buffer e la invia al client. Se la risposta non entra nei buffer di memoria, parte di essa può essere scritta in un file temporaneo. La documentazione ufficiale del modulo proxy di Nginx descrive questo comportamento e collega la scrittura di file temporanei a proxy_max_temp_file_size e proxy_temp_file_write_size.
Il primo buffer è controllato da proxy_buffer_size:
proxy_buffer_size 16k;
Questo buffer gestisce l'intestazione della risposta e la prima parte della risposta. Se il tuo upstream invia intestazioni grandi, come molti cookie, intestazioni di autenticazione grandi o metadati di tracciamento sovradimensionati, potresti vedere errori come "upstream sent too big header". In tal caso, proxy_buffer_size è spesso la direttiva da rivedere.
I buffer di risposta rimanenti sono controllati da proxy_buffers:
proxy_buffers 8 32k;
Ciò significa otto buffer da 32 KB ciascuno per i dati di risposta proxygati. Non significa che ogni richiesta consumi sempre l'intero importo dall'inizio alla fine, ma dovresti comunque trattare queste come impostazioni di memoria per richiesta sotto carico.
Un blocco comune di proxy inverso è simile a questo:
location /api/ {
proxy_pass http://app_backend;
proxy_buffering on;
proxy_buffer_size 16k;
proxy_buffers 8 32k;
proxy_busy_buffers_size 64k;
}
Non copiarlo ciecamente. Un piccolo sito HTML, un'API JSON e un servizio di download di file hanno esigenze diverse.
File temporanei e proxy_max_temp_file_size
Se il buffering delle risposte proxygate è abilitato e la risposta è più grande dei buffer configurati, Nginx può scrivere parte di essa su disco. proxy_max_temp_file_size limita la dimensione di quel file temporaneo. Il valore predefinito di Nginx è comunemente documentato come 1024m, e impostarlo a 0 disabilita il buffering delle risposte proxygate su file temporanei.
location /api/ {
proxy_pass http://app_backend;
proxy_buffering on;
proxy_max_temp_file_size 50m;
}
Usa 0 con cautela:
proxy_max_temp_file_size 0;
Ciò non significa che le risposte grandi diventino gratuite. Significa che Nginx non utilizzerà file temporanei per quelle risposte proxygate bufferizzate. A seconda della risposta, della velocità del client e del comportamento di buffering, potresti aver bisogno di più memoria, impostazioni di buffering diverse o un design di streaming.
Una sfumatura importante dalla documentazione di Nginx: la restrizione proxy_max_temp_file_size non si applica alle risposte che verranno memorizzate nella cache o su disco. Se usi proxy_cache o proxy_store, rivedi quelle impostazioni separatamente.
Quando proxy_buffering off è la risposta migliore
A volte non dovresti bufferizzare affatto la risposta. Endpoint di streaming, eventi inviati dal server, download di grandi dimensioni e output a lunga esecuzione spesso funzionano meglio con il buffering proxy disabilitato:
location /events/ {
proxy_pass http://app_backend;
proxy_buffering off;
}
Con il buffering disattivato, Nginx passa la risposta upstream al client man mano che la riceve invece di cercare di raccogliere la risposta completa. proxy_buffer_size conta ancora perché Nginx ha bisogno di un buffer per i dati ricevuti dall'upstream, ma proxy_buffers e il comportamento dei file temporanei diventano meno centrali.
Non disattivare il buffering a livello globale a meno che tu non capisca il compromesso. Il buffering protegge i server upstream dai client lenti. Se i client scaricano lentamente e il buffering è disattivato, le connessioni upstream potrebbero rimanere occupate più a lungo.
Buffer FastCGI per PHP e configurazioni simili
Se Nginx comunica con PHP-FPM, le direttive proxy_* non sono quelle di cui hai bisogno. FastCGI utilizza nomi corrispondenti:
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_buffer_size 16k;
fastcgi_buffers 8 32k;
}
Vale la stessa logica: il primo buffer gestisce intestazioni e dati iniziali; l'array di buffer gestisce più contenuto della risposta. Se PHP invia intestazioni grandi, fastcgi_buffer_size potrebbe richiedere attenzione. Se pagine PHP grandi vengono scritte su disco, rivedi fastcgi_buffers e il carico di lavoro.
Come ottimizzare senza indovinare
Inizia controllando i log:
sudo tail -f /var/log/nginx/error.log
Cerca avvisi relativi a corpi di richieste o risposte upstream bufferizzate su file temporanei. Quindi misura le dimensioni effettive coinvolte. Per le API, campiona le dimensioni delle richieste e delle risposte dai log di accesso, dai log dell'applicazione o dal tuo sistema di osservabilità. Per i caricamenti, separa le richieste di metadati dagli endpoint di caricamento file. Non dovrebbero condividere le stesse ipotesi.
Ottimizza nel contesto più ristretto che abbia senso. Invece di questo:
http {
client_body_buffer_size 10m;
}
preferisci qualcosa come questo solo per l'endpoint di caricamento:
location /upload/ {
client_max_body_size 50m;
client_body_buffer_size 512k;
proxy_pass http://upload_backend;
}
Per un'API JSON con occasionali risposte da 1 MB, potresti ottimizzare solo quella posizione API:
location /reports/ {
proxy_pass http://report_backend;
proxy_buffering on;
proxy_buffer_size 32k;
proxy_buffers 16 64k;
proxy_max_temp_file_size 20m;
}
Quindi calcola il caso peggiore. Se 1.000 richieste simultanee possono utilizzare ciascuna diverse centinaia di KB di buffer di risposta, questa è memoria reale. Lascia spazio per i processi worker, TLS, connessioni upstream, cache, la cache di pagina del sistema operativo e altri servizi.
Convalida la configurazione e monitora il sistema
Dopo ogni modifica:
sudo nginx -t
sudo systemctl reload nginx
Quindi monitora memoria, I/O su disco e log degli errori. Un test di sintassi riuscito prova solo che il file viene analizzato. Non prova che i valori siano buoni.
I controlli utili includono:
free -h
iostat -xz 1
sudo tail -f /var/log/nginx/error.log
Se gli avvisi di file temporanei scompaiono ma la pressione della memoria aumenta, hai esagerato. Se la memoria rimane sana e l'I/O su disco diminuisce durante il carico di lavoro interessato, probabilmente la modifica ha aiutato.
L'ottimizzazione dei buffer di Nginx funziona meglio quando è locale, misurata e legata al traffico reale. Mantieni le richieste ordinarie in memoria quando ciò fa risparmiare latenza, lascia che i caricamenti o download veramente grandi utilizzino il percorso corretto ed evita impostazioni globali che fanno pagare ogni connessione per il caso limite più grande.