Prestazioni PHP
Un PHP veloce si regola, non si compra.
Il nostro team lavora con PHP da venticinque anni e sa dove una richiesta perde il suo tempo. Lo troviamo sul vostro server e lo recuperiamo, uno strato alla volta.
Anatomia di una richiesta
Una pagina è una catena di attese: trovare il server, aprire la connessione, mettersi in coda per PHP, compilare, eseguire, interrogare il database, inviare. Passate da uno stato all'altro per vedere quali anelli accorciamo.
-
Risoluzione DNS Risponde il resolver del visitatore. Uguale in ogni stato: questa parte è la rete del visitatore. Uguale in ogni stato: questa parte è la rete del visitatore.
-
Connessione e TLS Una configurazione TLS datata, una nuova connessione per ogni file. TLS 1.3 e una sola connessione HTTP/2 riutilizzata per ogni file. TLS 1.3 e una sola connessione HTTP/2 riutilizzata per ogni file.
-
In attesa di un worker PHP Tutti i worker sono occupati, quindi la richiesta aspetta in coda. Un pool dimensionato sulla memoria: un worker libero è pronto. Non serve alcun worker PHP.
-
Compilazione degli script PHP legge e compila ogni file a ogni richiesta. OPcache serve gli script compilati dalla memoria. Niente da compilare.
-
Ricerca di file e classi L'autoloader scorre le directory per ogni classe. Un autoloader con classmap e una cache realpath già calda. Niente da caricare.
-
Esecuzione del vostro codice Il lavoro proprio dell'applicazione. Quasi uguale: il JIT raramente cambia questo in un sito tipico. Il vostro codice non viene eseguito per questa visita.
-
Query al database Indici mancanti, e la stessa query su ogni pagina. Indici aggiunti, risposte ripetute conservate in Redis. Nessuna query viene inviata.
-
Invio della pagina Inviata senza compressione. Compressa prima di lasciare il server. La pagina finita, direttamente dalla cache di pagina.
Le barre qui sopra sono un disegno. Questo numero è una misura.
Il resto del tempo è la distanza tra i nostri server in Germania e te. Quella parte dipende da dove sei, quindi misurala dalla tua connessione.
Misuralo dalla tua connessioneLe impostazioni che eliminano gran parte dell'attesa
Compilare, trovare i file e caricare le classi avviene prima che il vostro codice esegua una sola riga. Queste impostazioni fanno sì che accada una volta sola, non a ogni richiesta.
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0
opcache.preload=/srv/app/preload.php
opcache.preload_user=www-data
opcache.jit=tracing
opcache.jit_buffer_size=64M
realpath_cache_size=4096K
realpath_cache_ttl=600
$ composer install --no-dev --classmap-authoritative
-
OPcache
PHP compila ogni file prima di eseguirlo. OPcache conserva il risultato compilato in memoria condivisa, così la richiesta successiva salta quel lavoro. Dimensioniamo la sua memoria e il numero di file sul sito e leggiamo il tasso di successo per verificare che nulla venga scartato.
In produzione PHP smette di controllare le modifiche di ogni file; ogni deploy svuota la cache da sé.
-
Precaricamento
Per un framework, il precaricamento carica i file principali una sola volta all'avvio di PHP e li tiene pronti per ogni richiesta. Una modifica a quei file richiede un riavvio, per questo il riavvio fa parte del deploy.
-
JIT
Il JIT trasforma il codice PHP più eseguito in codice macchina. Aiuta il lavoro che tiene occupato il processore - elaborazione di immagini, calcoli, cicli lunghi. Un sito tipico passa il tempo ad aspettare il database e la rete, e lì il JIT cambia poco. Lo attiviamo dove una misura mostra una differenza, non ovunque.
-
Cache realpath
Ogni include chiede al disco dove si trovi davvero un file. Una cache realpath più grande tiene quelle risposte in memoria, e questo conta per i framework che caricano centinaia di file per richiesta.
-
Autoloader di Composer
Una classmap autoritativa trova ogni classe con una sola ricerca invece di scorrere le directory, e i pacchetti di sviluppo restano fuori dal server.
La manopola di regolazione: PHP-FPM
PHP-FPM mantiene un pool di worker PHP. Troppo pochi e le richieste fanno la coda; troppi e il server esaurisce la memoria e rallenta per tutti. Il pool si regola su ciò che il server ha davvero.
pm = static
pm.max_children = 64
Un numero fisso di worker, sempre attivi
Per un server dedicato a un solo sito molto visitato. Nessun tempo speso ad avviare processi, e la memoria usata è nota in anticipo.
pm = dynamic
pm.max_children = 64
pm.start_servers = 8
pm.min_spare_servers = 4
pm.max_spare_servers = 12
Alcuni worker pronti, altri sotto carico
Tiene alcuni worker inattivi in attesa e ne aggiunge altri quando il traffico sale, fino al limite. La scelta abituale per un sito con picchi giornalieri.
pm = ondemand
pm.max_children = 64
pm.process_idle_timeout = 10s
Worker solo quando arriva una richiesta
Per molti piccoli siti su un solo server: la memoria va ai siti visitati, e la prima richiesta dopo un periodo tranquillo paga un breve avvio.
Come fissiamo pm.max_children
pm.max_children = memoria per PHP ÷ memoria per worker
$ ps --no-headers -o rss -C php-fpm8.3 | awk '{s+=$1} END {print s/NR/1024 " MB"}'
Un esempio di calcolo, non una raccomandazione: i numeri veri vengono dal vostro server, e lasciamo margine per i picchi.
- pm.max_requests
- Rinnova un worker dopo un numero fissato di richieste, così una lenta perdita di memoria non diventa mai un disservizio.
- request_slowlog_timeout
- Una richiesta che dura di più scrive il suo stack trace nel log delle lentezze, che indica la funzione da esaminare.
- request_terminate_timeout
- Ferma una richiesta fuori controllo prima che blocchi un worker per sempre.
- pm.status_path
- Mostra la coda e quante volte il pool ha toccato il limite - il primo posto in cui guardiamo quando un sito rallenta nelle ore di punta.
Due strade dal web server a PHP
Li gestiamo entrambi e scegliamo in base a ciò che serve al sito. In ogni caso la richiesta più veloce è quella a cui PHP non deve mai rispondere.
LiteSpeed + LSAPI
- Visitatore
- LiteSpeed
- LSCachecache di pagina
- LSAPI
- lsphp
LiteSpeed comunica con PHP tramite il proprio protocollo LSAPI e conserva pagine intere in LSCache, che WordPress e altre applicazioni possono svuotare esattamente quando il contenuto cambia.
Nginx + PHP-FPM
- Visitatore
- Nginx
- fastcgi_cachecache di pagina
- FastCGI
- PHP-FPM
Nginx serve da sé i file statici, passa PHP a un pool PHP-FPM tramite un socket locale e può conservare le pagine finite nella sua cache FastCGI per i visitatori non autenticati.
-
Redis per oggetti e sessioni
Le risposte che una pagina chiede di continuo al database restano in memoria, e le sessioni smettono di toccare il disco.
-
Una cache di pagina intera
Un visitatore non autenticato riceve una pagina finita senza che PHP o il database vengano interpellati. Carrelli, account e pagamenti ne restano fuori.
-
HTTP/2 e compressione
Una sola connessione cifrata porta tutti i file della pagina, il testo viene compresso prima di partire e i file statici hanno lunghe durate di cache con nomi versionati.
-
Tempo nel database
Lo slow query log ed EXPLAIN mostrano quale query aspetta e perché; di solito un indice o una query ripetuta ne sono la parte maggiore.
Come ottimizziamo i database
Prima misuriamo, poi cambiamo una cosa sola
Ogni modifica si fa a confronto con una misura presa prima, così possiamo mostrarvi cosa ha prodotto. Queste sono le misure.
opcache_get_status()Tasso di successo, memoria libera e se gli script vengono espulsi.pm.status_pathLa coda di attesa e quante volte il pool ha raggiunto il limite.slowlogLo stack trace di ogni richiesta durata troppo.slow_query_log + EXPLAINQuali query aspettano, quante righe leggono e quale indice manca.redis-cli INFO statsSuccessi della cache contro mancati, e se le chiavi vengono espulse.curl -w %{time_starttransfer}Il tempo fino al primo byte, misurato allo stesso modo prima e dopo.
Volete l'altra metà del numero - la distanza tra i nostri server e voi? Avvia il test di velocità

Diteci quale pagina è lenta
Inviateci l'indirizzo e dove gira oggi. Prima leggiamo il server, poi vi diciamo cosa cambieremmo e cosa dovrebbe ottenere - e prepariamo il preventivo dopo quella conversazione.