Vai al contenuto principale

#php

PHP-FPM in produzione: perché pm static batte dynamic e come calcolare pm.max_children

Chi gestisce uno stack LEMP o LAMP in produzione conosce bene il dilemma: PHP-FPM va lasciato “respirare” con un process manager dinamico, oppure conv

Altro...

Chi gestisce uno stack LEMP o LAMP in produzione conosce bene il dilemma: PHP-FPM va lasciato “respirare” con un process manager dinamico, oppure conviene bloccare tutto su un numero fisso di worker? La risposta non è scontata, e sbagliarla ha un costo diretto in latenza durante i picchi di traffico. Vediamo perché, per molti carichi di lavoro, pm static è la scelta più solida — e come calcolare i parametri giusti senza affidarsi a numeri presi a caso da qualche post su Stack Overflow.

I tre process manager di PHP-FPMPHP-FPM gestisce i processi worker attraverso la direttiva pm nel file di pool (tipicamente /etc/php/8.x/fpm/pool.d/www.conf), che può assumere tre valori:

dynamic: il numero di processi figlio oscilla tra pm.min_spare_servers e pm.max_spare_servers, fino al tetto di pm.max_children. È il default su molte distribuzioni ed è pensato per bilanciare consumo di RAM e capacità di risposta.

ondemand: i processi vengono creati solo quando arriva una richiesta e vengono terminati dopo pm.process_idle_timeout secondi di inattività. Nessun worker “a riposo” occupa memoria, ma ogni nuovo processo comporta un fork che ha un costo in latenza.

static: il numero di worker è fissato esattamente a pm.max_children, tutti avviati all’avvio del servizio e mai terminati. Nessun fork a runtime, nessuna sorpresa.

La differenza pratica tra dynamic/ondemand e static si vede sotto carico: ogni volta che PHP-FPM deve forkare un nuovo processo per rispondere a un picco di richieste, quel fork costa tempo di CPU e, sotto pressione, può accodare le richieste in ingresso nel socket backlog. Su un server che serve traffico costante e prevedibile, questo overhead è puro spreco.

Perché “dynamic” può ingannareIl process manager dinamico sembra la scelta “intelligente” perché si adatta al carico. In realtà introduce una variabile in più proprio nei momenti in cui non la vorresti: durante un picco di traffico, quando i worker spare non bastano più, PHP-FPM deve forkarne di nuovi mentre il sistema è già sotto stress. Il risultato è un aumento di latenza percepibile negli access log, spesso confuso con un problema del database o del codice applicativo quando in realtà è il process manager stesso a introdurre il collo di bottiglia.

Quando usare pm staticLa regola pratica è semplice: se il server ha traffico sostenuto e memoria sufficiente per tenere tutti i worker sempre attivi, static è la scelta giusta. Se invece si gestiscono più pool PHP-FPM su un host condiviso con memoria limitata (tipico di ambienti multi-tenant o hosting condiviso), ondemand resta preferibile perché libera RAM quando il traffico cala.

; /etc/php/8.3/fpm/pool.d/www.conf
pm = static
pm.max_children = 40
pm.max_requests = 1000
Da notare pm.max_requests: anche in modalità static conviene non lasciarlo a 0 (illimitato). Un valore alto — 1000 richieste è un buon punto di partenza — fa sì che ogni worker venga riciclato periodicamente, mitigando eventuali memory leak nelle librerie PHP senza introdurre l’overhead di respawn continui tipico della modalità dinamica.

Come calcolare pm.max_children senza indovinareIl valore corretto di pm.max_children dipende da quanta RAM è disponibile e da quanta ne consuma mediamente un singolo worker PHP-FPM — un dato che varia moltissimo in base al framework (un’app Symfony con Doctrine pesa parecchio di più di uno script WordPress minimale).

Per misurare il consumo medio reale dei worker già in esecuzione:

ps --no-headers -o rss -C php-fpm8.3 | awk '{ sum += $1; n++ } END { print sum/n/1024 " MB" }'Questo comando interroga la RSS (Resident Set Size) di tutti i processi php-fpm attivi e ne calcola la media in MB. Con quel numero in mano, la formula diventa:

pm.max_children = (RAM dedicata a PHP-FPM in MB) / (RSS medio per worker in MB)Ad esempio, su un server con 6 GB di RAM riservati al pool PHP-FPM e worker che consumano in media 60 MB ciascuno:

6000 MB / 60 MB ≈ 100 workerÈ fondamentale non allocare a PHP-FPM tutta la RAM disponibile sulla macchina: bisogna lasciare margine per il sistema operativo, il web server (Nginx o Apache in front-end), la cache degli oggetti (Redis/Memcached) e, se presente sullo stesso host, il database. Una regola prudente è dedicare a PHP-FPM non più del 60-70% della RAM totale su un server dedicato al solo stack web.

Monitoraggio continuo, non solo calcolo una tantumIl consumo medio per worker cambia nel tempo — un deploy che introduce una libreria pesante, una query N+1 che gonfia il footprint di memoria, o semplicemente un aumento del traffico su endpoint più complessi. Vale la pena schedulare periodicamente il comando ps sopra (ad esempio via cron con output su un file di log, oppure integrandolo in un dashboard di monitoring come Netdata o Prometheus con node_exporter) per verificare che il valore di pm.max_children resti coerente con la realtà, invece di scoprirlo durante un incidente in produzione.

Un altro segnale da tenere d’occhio è il log di PHP-FPM stesso: se compaiono righe del tipo

WARNING: [pool www] server reached pm.max_children setting (40), consider raising itsignifica che il tetto è stato raggiunto e le richieste in eccesso stanno finendo in coda sul socket, con conseguente aumento del tempo di risposta. È il segnale più diretto per capire se il dimensionamento fatto a tavolino regge davvero sotto carico reale.

ConclusioneNon esiste un process manager “giusto” in assoluto: la scelta dipende dal profilo di traffico e dalla disponibilità di memoria. Ma per la maggior parte dei server di produzione con traffico prevedibile e risorse dedicate, pm static abbinato a un pm.max_children calcolato sul consumo reale di RSS — e non copiato da un tutorial generico — elimina una fonte di latenza spesso invisibile finché non arriva il picco di traffico che la rende evidente. Vale la pena rivedere la configurazione dei propri pool PHP-FPM con questo approccio, misurando prima di decidere.

Fonte: LinuxBlog.io — PHP-FPM tuning: Using ‘pm static’ for max performance

#linux #guide #howto #tutorial #performance #php

0

Caricamento...

0
1

Caricamento...

PHP-FPM: perché pm static batte dynamic e ondemand sui server ad alto traffico

Il process manager sbagliato è spesso il vero collo di bottiglia di PHPQuando un’applicazione PHP inizia a rallentare sotto traffico reale, la prima c

Altro...

Il process manager sbagliato è spesso il vero collo di bottiglia di PHPQuando un’applicazione PHP inizia a rallentare sotto traffico reale, la prima cosa che si tende a ottimizzare è il codice: query più efficienti, cache applicativa, opcache. Tutte cose giuste, ma c’è un parametro a monte che viene sistematicamente sottovalutato e che, su un server con traffico costante, può pesare quanto tutto il resto messo insieme: il process manager (PM) di PHP-FPM.

La quasi totalità delle installazioni lascia pm impostato su dynamic, il valore di default, oppure viene consigliato di passare a ondemand quando la memoria disponibile è scarsa. Per un server che riceve traffico costante e prevedibile, però, esiste una terza opzione quasi sempre trascurata: pm = static. In questo articolo vediamo perché, come calcolare i parametri corretti e in quali scenari conviene davvero.

Le tre modalità del process managerPHP-FPM gestisce un pool di processi worker che eseguono lo script PHP per ogni richiesta. Il parametro pm nel file di configurazione del pool decide come questi worker vengono creati e distrutti nel tempo:

dynamic: il numero di processi figli varia dinamicamente in base a pm.max_children, pm.start_servers, pm.min_spare_servers e pm.max_spare_servers. All’avvio del servizio vengono lanciati pm.start_servers worker, poi il pool si espande e si contrae seguendo il carico.

ondemand: i processi vengono avviati solo quando arriva una richiesta, invece di essere già pronti all’avvio del servizio come accade con dynamic. Ottimo per il risparmio di memoria, meno per la latenza sul primo hit.

static: il numero di processi figli è fisso, determinato unicamente da pm.max_children. Nessuna logica di scaling: i worker vengono creati all’avvio e restano attivi.

La documentazione ufficiale di PHP elenca tutte le direttive globali di php-fpm.conf, ma la scelta tra queste tre modalità è più una questione di architettura del server che di singola direttiva.

Un parallelo utile: il governor della CPUChiunque abbia mai armeggiato con le impostazioni di risparmio energetico della CPU (CPUFreq governor, presenti sia su *nix che su Windows) riconoscerà lo stesso identico compromesso:

ondemand: scala la frequenza dinamicamente in base al carico corrente, saltando rapidamente alla frequenza massima per poi scendere durante i periodi di inattività.

conservative: scala la frequenza in modo più graduale rispetto a ondemand.

performance: mantiene sempre la CPU alla frequenza massima.

Il compromesso è lo stesso che si ritrova in PHP-FPM: un’impostazione privilegia la reattività immediata, le altre il risparmio di risorse durante i periodi di inattività. Con il governor performance, i core mantengono la frequenza massima invece di scalare verso il basso in idle: è un boost di prestazioni relativamente sicuro, il cui costo dipende quasi solo dai limiti termici della CPU. Lo stesso principio, applicato ai processi PHP-FPM invece che ai cicli di clock, è ciò che rende pm = static efficace su un server sotto carico costante.

Usare pm static per il massimo delle prestazioniL’impostazione pm = static dipende fortemente dalla memoria libera disponibile sul server. Se la memoria è scarsa, ondemand o dynamic restano scelte più sicure. Se invece la memoria è disponibile, static elimina gran parte dell’overhead di gestione del pool impostando il numero di worker al massimo che il server può sostenere.

In pratica, pm.max_children con static va calcolato come il numero massimo di processi PHP-FPM che possono girare senza generare pressione sulla memoria disponibile o sulla cache del sistema, e senza saturare le CPU con una coda di operazioni PHP-FPM in attesa.

Un caso reale: un server con 32 GB di RAM installata, pm = static e pm.max_children = 100, usa un massimo di circa 10 GB. Anche con circa 200 utenti attivi negli ultimi 60 secondi (dato preso da Google Analytics), circa il 70% dei worker PHP-FPM resta idle. Questo è precisamente il punto: PHP-FPM lavora sempre alla capacità massima configurata, indipendentemente dal traffico istantaneo, e i worker idle restano pronti a rispondere immediatamente ai picchi di traffico invece di dover attendere che il process manager ne generi di nuovi (e poi li termini dopo che pm.process_idle_timeout scade).

Calcolare pm.max_children, non indovinarloIl passaggio più importante, spesso saltato, è misurare invece di stimare a occhio. Prima si misura la dimensione media residente (RSS) di un worker PHP-FPM sotto carico reale:

ps --no-headers -o rss -C php-fpm | awk '{ sum += $1; n++ } END { print sum/n/1024 " MB" }'A questo punto si divide la memoria che si è disposti a dedicare a PHP-FPM per questo valore medio. Se un worker occupa in media 60 MB e si vogliono allocare 6 GB a PHP-FPM, il calcolo è circa pm.max_children = 100 (6144 / 60). È fondamentale lasciare margine per il sistema operativo, il web server e il database: non va mai assegnata a PHP-FPM tutta la RAM fisica disponibile.

Da qui si procede per iterazioni: si testa sotto carico reale e si affina il valore osservando uso di memoria, utilizzo CPU e tempi di risposta. Con pm = static, poiché i worker restano già residenti in memoria, i picchi di traffico si traducono in picchi di carico e CPU molto più contenuti, e le medie restano più stabili nel tempo.

Per monitorare i processi attivi in tempo reale si può usare top filtrato per utente:

top -bn1 | grep php-fpmImpostando anche pm.max_requests a un valore alto (o a 0 per disabilitare il riavvio periodico dei worker) si evita ulteriore overhead di gestione, ma questa scelta ha senso solo su un server di produzione senza memory leak noti negli script PHP. In generale conviene comunque impostare un valore alto ma finito, ad esempio pm.max_requests = 1000, per garantire un riavvio periodico dei worker senza reintrodurre overhead significativo.

Quando usare ondemand e dynamic invece di staticCon pm = dynamic capita spesso di incontrare un warning simile a questo nei log:

WARNING: [pool xxxx] seems busy (you may need to increase pm.start_servers,
or pm.min/max_spare_servers), spawning 32 children, there are 4 idle,
and 59 total childrenIl consiglio più comune, in questi casi, è passare a ondemand. Su un server costantemente sotto carico, però, questa scelta si ritorce contro: ondemand azzera i worker idle appena il traffico cala, per poi doverli rigenerare non appena il traffico torna a salire, scambiando risparmio di memoria con latenza di spawn esattamente nel momento peggiore. Un timeout di idle molto alto attenua il problema, ma a quel punto conviene semplicemente passare a pm.static con un pm.max_requests elevato.

dynamic e soprattutto ondemand restano invece la scelta giusta in scenari con molti pool PHP-FPM distinti sullo stesso server, ad esempio hosting condiviso con centinaia di account cPanel o siti diversi ciascuno con il proprio pool. In un ambiente con 100+ pool e 200+ domini, dove la maggior parte dei siti riceve pochissimo traffico, static o dynamic sprecherebbero enormi quantità di memoria su worker perennemente idle: ondemand chiude i worker inattivi liberando memoria, motivo per cui è diventato il default in ambienti come cPanel.

Un cenno ai containerLo stesso ragionamento va adattato quando PHP-FPM gira dentro un container con risorse limitate (ad esempio 0.5 vCPU e 1 GB di RAM) e la scalabilità è orizzontale, tramite orchestrazione (Docker Swarm, Kubernetes). In questi contesti pm.static resta spesso l’unica scelta sensata a livello di singolo container, ma la decisione su quando far partire un nuovo container non può basarsi solo su CPU e memoria: va monitorato anche il numero di processi PHP-FPM attivi rispetto a pm.max_children. Se il pool ha 50 worker configurati e 40 sono già occupati, è il momento di avviare un nuovo container, indipendentemente da quanto CPU e RAM stiano effettivamente segnalando in quel momento. Questo richiede di esporre lo stato di php-fpm status al sistema di autoscaling, valutato a intervalli brevi.

ConclusioneSuperata una certa soglia di traffico costante, ondemand e dynamic introducono un overhead di gestione dei processi che una configurazione static ben calcolata elimina alla radice. La regola pratica è semplice da enunciare ma richiede disciplina nell’applicarla: non indovinare pm.max_children, misurarlo a partire dal consumo medio di RSS dei worker sotto carico reale, lasciare margine per OS, web server e database, e poi affinare osservando le metriche reali. Su un server dedicato o una VM con traffico prevedibile, il guadagno in stabilità di CPU e tempi di risposta è concreto. Su hosting condiviso con centinaia di pool a bassissimo traffico, ondemand resta la scelta più razionale. Conoscere il proprio sistema, prima ancora del proprio codice PHP, è ciò che fa la differenza.

Fonte: PHP-FPM tuning: Using ‘pm static’ for max performance, LinuxBlog.io (Hayden James)

#linux #howto #performance #php

1

Caricamento...

0
1

Caricamento...