...

Configurare correttamente la gestione dei processi PHP-FPM: spiegazione di pm.max_children & Co.

Ottimizzazione php-fpm decide quanti processi PHP-FPM possono essere eseguiti contemporaneamente, la velocità con cui vengono avviati i nuovi processi e per quanto tempo questi ultimi gestiscono le richieste. Ti mostrerò come pm.max_children, pm, pm.start_servers, pm.min_spare_servers, pm.max_spare_servers e pm.max_requests in modo tale che la tua applicazione risponda rapidamente sotto carico e il server non entri in swapping.

Punti centrali

  • modalità pm: scegli correttamente tra static, dynamic o ondemand, in modo che i processi siano adeguati al tuo traffico.
  • pm.max_children: Allineare il numero di processi PHP simultanei alla RAM e al consumo effettivo di risorse.
  • Valori di avvio/riserva: bilanciare in modo ragionevole pm.start_servers, pm.min_spare_servers, pm.max_spare_servers.
  • riciclaggio: Con pm.max_requests è possibile attenuare le perdite di memoria senza generare un sovraccarico inutile.
  • Monitoraggio: Tenere sotto controllo log, stato e RAM, quindi regolare gradualmente.

Perché la gestione dei processi è importante

Contribuisco con PHP-FPM l'esecuzione di ogni script PHP come processo separato e ogni richiesta parallela richiede un proprio worker. Senza limiti adeguati, le richieste in coda si bloccano, causando Timeout e errori. Se imposto limiti troppo alti, il pool di processi consuma la memoria di lavoro e il kernel inizia a scambiare. Questo equilibrio non è un gioco d'ipotesi: mi baso su valori di misurazione reali e mantengo un margine di sicurezza. In questo modo la latenza rimane bassa e il throughput stabile, anche quando il carico aumenta.

Per me è importante una chiara valore obiettivo: Quante esecuzioni PHP simultanee voglio consentire senza esaurire la RAM? Allo stesso tempo, verifico se i colli di bottiglia si verificano più spesso nella Banca dati, nelle API esterne o nel server web. Solo se conosco il collo di bottiglia, posso scegliere i valori corretti per pm, pm.max_children e simili. Inizio in modo conservativo, misuro e poi aumento gradualmente. In questo modo evito riavvii difficili e guasti imprevisti.

Le tre modalità pm: static, dynamic, ondemand

La modalità statico mantiene sempre esattamente pm.max_children processi disponibili. Ciò garantisce latenze molto prevedibili, poiché non è necessario alcun processo di avvio. Utilizzo static quando il carico è molto uniforme e la RAM disponibile è sufficiente. Tuttavia, in caso di domanda variabile, con static spreco facilmente Memoria. Per questo motivo utilizzo static in modo mirato laddove necessito di un'esecuzione costante.

Con dinamico Avvio una quantità iniziale e lascio che la dimensione del pool oscilli tra min_spare e max_spare. Questa modalità è adatta al traffico con picchi, perché i worker vengono creati e chiusi in base alle necessità. Mantengo sempre un numero sufficiente di processi inattivi per far fronte ai picchi senza tempi di attesa. Tuttavia, un numero eccessivo di worker inattivi occupa inutilmente risorse. RAM, motivo per cui mantengo il margine di risparmio ridotto. In questo modo la piscina rimane mobile senza gonfiarsi.

In modalità a richiesta Inizialmente non esistono worker, PHP-FPM li avvia solo in caso di richieste. Ciò consente di risparmiare memoria nelle fasi di inattività, ma il primo accesso comporta una certa latenza. Scelgo ondemand per pool utilizzati raramente, strumenti di amministrazione o endpoint cron. Per i siti web molto frequentati, ondemand offre solitamente tempi di risposta peggiori. In questi casi preferisco chiaramente dynamic con valori di riserva impostati in modo corretto.

Dimensionare correttamente pm.max_children

Credo che pm.max_children dalla RAM disponibile per PHP e dalla memoria media per worker. A tal fine, riservo innanzitutto memoria per il sistema, il server web, il database e le cache, in modo che il sistema non entri in Outsourcing funziona. Divido la RAM rimanente per il consumo effettivo misurato del processo. Dalla teoria sottraggo un margine di sicurezza di 20-30 % per compensare i valori anomali e i picchi di carico. Utilizzo il risultato come valore iniziale e poi osservo l'effetto.

Calcolo il consumo medio di processo con strumenti quali ps, top o htop e controllo RSS/RES. Importante: misuro sotto carico tipico, non al minimo. Se carico molti plugin, framework o librerie di grandi dimensioni, il consumo per worker aumenta notevolmente. Inoltre, la CPU limita la curva: più processi non aiutano se una A filo singolo-Prestazioni della CPU limitate per ogni richiesta. Chi desidera approfondire le caratteristiche della CPU troverà informazioni di base su Prestazioni single-thread.

Mantengo trasparenti le mie ipotesi: quanta RAM è realmente disponibile per PHP? Quanto è grande un worker nelle richieste tipiche? Quali picchi si verificano? Se le risposte sono corrette, imposto pm.max_children, eseguo un reload graduale e controllo la RAM, i tempi di risposta e i tassi di errore. Solo dopo procedo con piccoli passi verso l'alto o verso il basso.

Valori indicativi in base alle dimensioni del server

La tabella seguente mi fornisce valori iniziali alla mano. Non sostituisce la misurazione, ma fornisce un orientamento solido per le prime impostazioni. Adatto i valori a ciascuna applicazione e li controllo con il monitoraggio. Se le riserve rimangono inutilizzate, aumento cautamente. Se il server raggiunge il limite della RAM, riduco i valori.

RAM del server RAM per PHP Ø MB/lavoratore pm.max_children (Inizio) Utilizzo
1-2 GB ~1 GB 50–60 15–20 Piccoli siti, blog
4–8 GB ~4–6 GB 60–80 30–80 Affari, piccoli negozi
16+ GB ~10–12 GB 70–90 100–160 Carichi elevati, API, negozi

Leggo la tabella da destra a sinistra: Il Utilizzo Per quanto riguarda il progetto, verifico che la RAM sia riservata in modo realistico per PHP. Quindi seleziono una dimensione del worker adeguata al codice base e alle estensioni. Successivamente imposto pm.max_children e osservo l'effetto durante il funzionamento live. Il tasso di successo e la stabilità aumentano se documento accuratamente questi passaggi.

Impostare i valori di avvio, di riserva e di richiesta

Con pm.avvia_server Stabilisco quanti processi devono essere immediatamente disponibili. Un valore troppo basso genera avvii a freddo sotto carico, mentre uno troppo alto occupa inutilmente RAM. Mi baso spesso su 15-30 % di pm.max_children e arrotondo se il carico inizia in modo piuttosto tranquillo. In caso di picchi di traffico, scelgo una quantità iniziale leggermente superiore, in modo che le richieste non arrivino prima che ci siano abbastanza worker in attesa. Questa regolazione fine riduce significativamente il tempo di risposta iniziale.

I valori pm.min_spare_servers e pm.max_spare_servers definiscono l'intervallo di inattività. Mantengo un numero di worker liberi tale da consentire l'accesso immediato alle nuove richieste, ma non così tanti da sprecare memoria con i processi inattivi. Per i negozi online preferisco impostare una finestra più ristretta per appianare i picchi. Con pm.max_requests Riciclo i processi dopo alcune centinaia di richieste per limitare la deriva della memoria. Per applicazioni poco appariscenti scelgo 500-800, mentre in caso di sospette perdite scelgo volutamente valori inferiori.

Monitoraggio e risoluzione dei problemi

Controllo regolarmente Registri, pagine di stato e RAM. Gli avvisi relativi al raggiungimento dei limiti pm.max_children sono per me un chiaro segnale che è necessario aumentare il limite massimo o ottimizzare il codice/DB. Se si verificano errori 502/504 frequenti, controllo i log del server web e le code. Fluttuazioni significative della latenza indicano un numero insufficiente di processi, I/O bloccanti o costi di processo troppo elevati. Prima guardo i fatti concreti e poi reagisco con piccoli passi, mai con salti XXL.

Riconosco più rapidamente i colli di bottiglia se utilizzo il Tempi di attesa Misuro lungo l'intera catena: server web, PHP-FPM, database, servizi esterni. Se il tempo di backend aumenta solo per determinati percorsi, isolo le cause tramite profiling. Se i tempi di attesa si verificano ovunque, intervengo sulla dimensione del server e del pool. È utile anche dare un'occhiata alle code dei worker e ai processi in stato D. Solo quando ho compreso la situazione modifico i limiti e documento accuratamente ogni modifica.

Interazione tra server web e PHP-FPM

Mi assicuro che Server web-Limiti e PHP-FPM sono in armonia. Troppe connessioni simultanee nel server web con troppo pochi worker causano code e timeout. Se i worker sono impostati al massimo, ma il server web limita l'accettazione, le prestazioni rimangono basse. Parametri come worker_connections, event-Loop e Keep-Alive hanno un effetto diretto sul carico PHP. Un'introduzione pratica alla messa a punto è fornita dai suggerimenti su Thread pool nel server web.

Conservo Mantenere in vita-Finestra temporale in vista, in modo che le connessioni inattive non blocchino inutilmente i worker. Per le risorse statiche, imposto un caching aggressivo prima del PHP, per tenere il carico di lavoro lontano dal pool. Le cache reverse proxy aiutano ulteriormente quando vengono richiamate spesso risposte identiche. In questo modo posso mantenere pm.max_children più basso e comunque fornire risultati più rapidi. Meno lavoro per ogni richiesta è spesso la leva più efficace.

Viti di regolazione di precisione in php-fpm.conf

Vado oltre i valori fondamentali e adeguo il Parametri della piscina bene. Con pm.max_spawn_rate limito la velocità con cui possono essere creati nuovi worker, in modo che il server non avvii processi in modo troppo aggressivo durante i picchi di carico e non finisca in CPU thrashing. Per ondemand imposto con pm.process_idle_timeout fisso, quanto velocemente scompaiono i worker inutilizzati: troppo breve genera overhead di avvio, troppo lungo occupa RAM. Nel caso di ascoltare-Socket, posso scegliere tra Unix-Socket e TCP. Un Unix-Socket riduce il sovraccarico e offre un'assegnazione chiara dei diritti tramite listen.owner, listen.group e listen.mode. Per entrambe le varianti imposto listen.backlog sufficientemente alto affinché i burst in arrivo finiscano nel buffer del kernel invece di essere immediatamente respinti. Con rlimit_files Se necessario, aumento il numero di file aperti per ogni worker, il che garantisce stabilità in caso di numerosi upload e download simultanei. E se servono delle priorità, utilizzo process.priority, per trattare in modo leggermente subordinato i pool poco critici dal punto di vista della CPU.

Slowlog e protezione dai blocchi

Per rendere visibili le richieste complesse, attivo il Slowlog. Con timeout_richiesta_slowlog definisco la soglia (ad es. 2-3 s) a partire dalla quale uno stack trace viene inserito nel slowlog . In questo modo trovo I/O bloccanti, loop costosi o blocchi imprevisti. Contro i veri blocchi utilizzo timeout_richiesta_termine, che si interrompe bruscamente quando una richiesta dura troppo a lungo. Ritengo che questi intervalli di tempo siano coerenti con tempo_di_esecuzione_max da PHP e dai timeout del server web, in modo che un livello non si interrompa prima dell'altro. In pratica, inizio in modo conservativo, analizzo gli slowlog sotto carico e adeguo gradualmente le soglie fino a quando i segnali sono significativi, senza sovraccaricare il log.

Opcache, memory_limit e la loro influenza sulle dimensioni dei worker

Mi riferisco al Opcache nella mia pianificazione della RAM. La sua area di memoria condivisa non viene conteggiata per ogni worker, ma viene utilizzata in comune da tutti i processi. Dimensione e frammentazione (opcache.memory_consumption, interned_strings_buffer) influenzano notevolmente il tempo di riscaldamento e la percentuale di successo. Un Opcache ben dimensionato riduce la pressione sulla CPU e sulla RAM per ogni richiesta, poiché viene ricompilato meno codice. Allo stesso tempo, noto che limite_di_memoria: un valore elevato protegge dall'esaurimento della memoria in singoli casi, ma aumenta il budget teorico peggiore per ogni worker. Per questo motivo pianifico con una media misurata più un buffer, non con il semplice memory_limit. Funzionalità come il precaricamento o JIT aumentano il fabbisogno di memoria: le testiamo in modo mirato e calcoliamo il consumo aggiuntivo nel calcolo pm.max_children.

Separare e dare priorità ai pool

Divido le applicazioni in più piscine quando i profili di carico differiscono notevolmente. Un pool per il traffico frontend, uno per l'amministrazione/backend, un terzo per cron/upload: in questo modo isolo i picchi e assegno limiti differenziati. Per gli endpoint poco frequentati imposto a richiesta con un breve timeout di inattività, per il frontend dinamico con un margine di risparmio ridotto. Informazioni su utente/gruppo e, se necessario,. chroot mi occupo di garantire un isolamento pulito, mentre i diritti socket regolano quale processo del server web può accedere. Quando sono richieste delle priorità, il frontend riceve più pm.max_children e, se necessario, una neutrale process.priority, mentre Cron/Reports funzionano con un budget inferiore e una priorità minore. In questo modo l'interfaccia utente rimane reattiva anche quando in background sono in esecuzione lavori pesanti.

Utilizzare correttamente gli endpoint di stato

Per la diagnosi della durata attivo pm.status_path e opzionale ping.path per pool. Nello stato vedo Active/Idle-Worker che Coda delle liste, contatori basati sulla velocità effettiva e metriche delle richieste lente. Una coda di lista in costante crescita o lavoratori inattivi costantemente pari a 0 sono per me segnali di allarme. Proteggo questi endpoint dietro Auth e una rete interna, in modo che nessun dettaglio operativo venga divulgato all'esterno. Inoltre, attivo catch_workers_output, quando voglio raccogliere rapidamente stdout/stderr dai worker, ad esempio in caso di errori difficili da riprodurre. Combino questi segnali con le metriche di sistema (RAM, CPU, I/O) per decidere se aumentare pm.max_children, aggiornare i valori di riserva o intervenire sull'applicazione.

Caratteristiche speciali nei container e nelle VM

All'indirizzo contenitori e piccole VM, faccio attenzione ai limiti cgroup e al rischio dell'OOM killer. Impostato pm.max_children rigorosamente in base al Limite di memoria del contenitore e testare i picchi di carico, in modo che nessun worker venga chiuso. Senza swap nei container, il margine di sicurezza è particolarmente importante. Per le quote CPU, scalizzo il numero di worker in base al numero di vCPU disponibili: se l'applicazione è CPU-bound, una maggiore parallelità porta più code che throughput. I carichi di lavoro IO-bound tollerano più processi, purché il budget RAM lo consenta. Inoltre, imposto soglia_riavvio_emergenza e intervallo_riavvio_emergenza per il processo master, al fine di evitare una spirale di crash nel caso in cui un bug raro causi il blocco di più processi figli in breve tempo. In questo modo il servizio rimane disponibile mentre analizzo la causa.

Implementazioni e ricaricamenti senza intoppi e senza interruzioni

Sto progettando Ricariche in modo tale che le richieste in corso vengano portate a termine correttamente. Un ricaricamento graduale (ad es. tramite systemd reload) applica le nuove configurazioni senza interrompere bruscamente le connessioni aperte. Mantengo stabile il percorso del socket in modo che il server web non rilevi alcuna interruzione della connessione. In caso di cambi di versione che invalidano gran parte dell'Opcache, preriscaldo la cache (preloading/warmup-requests) per limitare i picchi di latenza subito dopo il deployment. Prima di implementare i valori su larga scala, testo le modifiche più significative su un pool più piccolo o in un'istanza Canary con configurazione identica. Ogni modifica viene registrata nel mio log delle modifiche con timestamp e screenshot delle metriche, il che riduce il tempo necessario per individuare eventuali errori in caso di effetti collaterali imprevisti.

Comportamento burst e code di attesa

Affronto i picchi di carico con un sistema coordinato Progettazione delle code . Io metto listen.backlog così alto che il kernel può temporaneamente bufferizzare più tentativi di connessione. Sul lato server web, limito il numero massimo di connessioni FastCGI simultanee per pool in modo che siano pm.max_children adatto. In questo modo, i burst si accumulano preferibilmente nel server web (economico) piuttosto che in profondità nel PHP (costoso). Misuro il Coda delle liste nello stato FPM: se aumenta regolarmente, aumento il numero di worker, ottimizzo i tassi di cache hit o riduco i valori keep-alive aggressivi. L'obiettivo è quello di mantenere il Tempo al primo byte mantenere stabile, invece di lasciare che le richieste finiscano in code infinite.

Flusso di lavoro pratico per le regolazioni

Inizio con un Audit: budget RAM, dimensione del processo, profili I/O. Successivamente, imposto valori iniziali conservativi per pm.max_children e la modalità pm. Quindi eseguo test di carico o osservo i picchi di traffico reali. Registro tutte le modifiche, comprese le metriche e le finestre temporali. Dopo ogni regolazione, controllo la RAM, la latenza P50/P95 e i tassi di errore: solo allora passo alla fase successiva.

Quando mi trovo ripetutamente al limite, non reagisco immediatamente in modo esagerato. Lavoratore-Numero. Per prima cosa ottimizzo le query, i tassi di cache e le funzioni costose. Sposta i lavori IO-heavy nelle code e accorcia i percorsi di risposta. Solo quando l'applicazione funziona in modo efficiente, aumento la dimensione del pool. Questo processo consente di risparmiare risorse ed evitare danni consequenziali in altri punti.

Scenari tipici: valori esemplificativi

Su un vServer da 2 GB riserviamo circa 1 GB per PHP-FPM e imposto un consumo worker di circa 50-60 MB. In questo modo inizio con pm.max_children a 15-20 e utilizzo dynamic con una quantità iniziale ridotta. Mantengo min_spare a 2-3 e max_spare a 5-6. Impostato pm.max_requests a 500, in modo che i processi vengano scambiati regolarmente. Queste impostazioni garantiscono tempi di risposta stabili per i progetti di piccole dimensioni.

Con 8 GB di RAM, di solito pianifico 4-6 GB per PHP e imposto dimensioni dei worker comprese tra 60 e 80 MB. Ne risultano 30-80 processi figli come area di avvio. pm.start_servers è compreso tra 15 e 20, min_spare tra 10 e 15, max_spare tra 25 e 30. Per pm.max_requests scelgo un valore compreso tra 500 e 800. Sotto carico, verifico se il picco di RAM lascia margine e poi aumento con cautela.

In configurazioni ad alto carico con 16+ GB di RAM, riservo 10-12 GB per FPM. Con 70-90 MB per worker, arrivo rapidamente a 100-160 processi. Che sia più opportuno utilizzare static o dynamic dipende dal tipo di carico. Per un carico elevato costante è preferibile static, mentre per una domanda altalenante è meglio dynamic. In entrambi i casi è necessario un monitoraggio costante.

Evitare gli ostacoli e stabilire le priorità

Non confondo il numero dei Visitatori con il numero di script PHP simultanei. Molte visualizzazioni di pagine colpiscono le cache, forniscono file statici o bloccano al di fuori di PHP. Per questo motivo dimensiono pm.max_children in base al tempo PHP misurato, non alle sessioni. Se i processi sono impostati in modo troppo parsimonioso, vedo richieste in attesa e tassi di errore in aumento. Se i valori sono troppo alti, la memoria si riversa nello swap e tutto rallenta.

Un errore comune: più processi equivalgono a più Velocità. In realtà, ciò che conta è l'equilibrio tra CPU, IO e RAM. Se la CPU raggiunge il 100% % e la latenza aumenta rapidamente, l'aggiunta di ulteriori worker non è di grande aiuto. È meglio eliminare il vero collo di bottiglia o ridurre il carico tramite la cache. Il motivo per cui i worker sono spesso il collo di bottiglia è spiegato nella guida su PHP Worker come collo di bottiglia.

Riassumendo brevemente

Prima calcolo il valore reale RAM-Consumo per worker e imposto pm.max_children con buffer. Quindi seleziono la modalità pm adatta al tipo di carico e bilanccio i valori di avvio e di riserva. Con pm.max_requests mantengo i processi aggiornati, senza overhead inutili. Invio log, stati e metriche a un sistema di monitoraggio pulito, in modo che ogni modifica rimanga misurabile. In questo modo ottengo tempi di risposta brevi, pool stabili e un carico del server che ha riserve per i picchi.

Articoli attuali