Performances PHP
Un PHP rapide se règle, il ne s'achète pas.
Notre équipe travaille avec PHP depuis vingt-cinq ans et sait où une requête perd son temps. Nous le retrouvons sur votre serveur et le récupérons, couche par couche.
Anatomie d'une requête
Une page est une chaîne d'attentes : trouver le serveur, ouvrir la connexion, attendre PHP, compiler, exécuter, interroger la base de données, envoyer. Passez d'un état à l'autre pour voir quels maillons nous raccourcissons.
-
Résolution DNS Assurée par le résolveur du visiteur. Identique dans tous les états : cette partie est le réseau du visiteur. Identique dans tous les états : cette partie est le réseau du visiteur.
-
Connexion et TLS Une configuration TLS ancienne, une nouvelle connexion pour chaque fichier. TLS 1.3 et une seule connexion HTTP/2 réutilisée pour chaque fichier. TLS 1.3 et une seule connexion HTTP/2 réutilisée pour chaque fichier.
-
Attente d'un worker PHP Tous les workers sont occupés, la requête attend dans la file. Un pool dimensionné selon la mémoire : un worker libre est prêt. Aucun worker PHP n'est nécessaire.
-
Compilation des scripts PHP lit et compile chaque fichier à chaque requête. OPcache sert les scripts compilés depuis la mémoire. Rien à compiler.
-
Recherche des fichiers et des classes L'autoloader parcourt les répertoires pour chaque classe. Un autoloader à classmap et un cache realpath déjà rempli. Rien à charger.
-
Exécution de votre code Le travail propre de l'application. À peu près pareil : le JIT change rarement cela pour un site typique. Votre code ne s'exécute pas pour cette visite.
-
Requêtes en base de données Des index manquants, et la même requête sur chaque page. Index ajoutés, réponses répétées gardées dans Redis. Aucune requête n'est envoyée.
-
Envoi de la page Envoyée sans compression. Compressée avant de quitter le serveur. La page terminée, directement depuis le cache de pages.
Les barres ci-dessus sont un dessin. Ce chiffre est une mesure.
Le reste du temps, c'est la distance entre nos serveurs en Allemagne et vous. Cette part dépend de l'endroit où vous êtes : mesurez-la depuis votre propre connexion.
Mesurez depuis votre connexionLes réglages qui suppriment l'essentiel de l'attente
Compiler, trouver les fichiers et charger les classes se fait avant que votre code n'exécute une seule ligne. Ces réglages font que cela n'arrive qu'une fois, et non à chaque requête.
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 compile chaque fichier avant de l'exécuter. OPcache garde le résultat compilé en mémoire partagée, et la requête suivante saute ce travail. Nous dimensionnons sa mémoire et son nombre de fichiers selon le site, et lisons son taux de succès pour vérifier que rien n'est évincé.
En production, PHP cesse de vérifier chaque fichier ; chaque déploiement vide lui-même le cache.
-
Préchargement
Pour un framework, le préchargement charge ses fichiers principaux une seule fois au démarrage de PHP et les garde prêts pour chaque requête. Modifier ces fichiers exige un redémarrage, qui fait donc partie du déploiement.
-
JIT
Le JIT transforme le code PHP le plus sollicité en code machine. Il aide les tâches qui occupent le processeur - traitement d'images, calculs, longues boucles. Un site typique passe son temps à attendre la base de données et le réseau, et là le JIT change peu de chose. Nous l'activons là où une mesure montre une différence, pas partout.
-
Cache realpath
Chaque include demande au disque où se trouve réellement un fichier. Un cache realpath plus grand garde ces réponses en mémoire, ce qui compte pour les frameworks qui chargent des centaines de fichiers par requête.
-
Autoloader Composer
Une classmap faisant autorité trouve chaque classe en une seule recherche au lieu de parcourir les répertoires, et les paquets de développement restent hors du serveur.
Le bouton de réglage : PHP-FPM
PHP-FPM entretient un pool de workers PHP. Trop peu, et les requêtes font la queue ; trop, et le serveur manque de mémoire et ralentit pour tout le monde. Le pool se règle sur ce que le serveur possède réellement.
pm = static
pm.max_children = 64
Un nombre fixe de workers, toujours actifs
Pour un serveur dédié à un seul site très fréquenté. Aucun temps perdu à lancer des processus, et la mémoire utilisée est connue d'avance.
pm = dynamic
pm.max_children = 64
pm.start_servers = 8
pm.min_spare_servers = 4
pm.max_spare_servers = 12
Quelques workers prêts, davantage sous charge
Garde quelques workers en attente et en ajoute quand le trafic monte, jusqu'à la limite. Le choix habituel pour un site aux pics quotidiens.
pm = ondemand
pm.max_children = 64
pm.process_idle_timeout = 10s
Des workers seulement à l'arrivée d'une requête
Pour de nombreux petits sites sur un même serveur : la mémoire va aux sites visités, et la première requête après une période calme paie un court démarrage.
Comment nous fixons pm.max_children
pm.max_children = mémoire pour PHP ÷ mémoire par worker
$ ps --no-headers -o rss -C php-fpm8.3 | awk '{s+=$1} END {print s/NR/1024 " MB"}'
Un exemple de calcul, pas une recommandation : les vrais chiffres viennent de votre serveur, et nous gardons une marge pour les pics.
- pm.max_requests
- Renouvelle un worker après un nombre donné de requêtes, pour qu'une lente fuite de mémoire ne devienne jamais une panne.
- request_slowlog_timeout
- Une requête qui dure plus longtemps écrit sa pile d'appels dans le journal des lenteurs, qui désigne la fonction à examiner.
- request_terminate_timeout
- Arrête une requête incontrôlée avant qu'elle ne bloque un worker pour de bon.
- pm.status_path
- Montre la file d'attente et la fréquence à laquelle le pool a atteint sa limite - le premier endroit que nous regardons quand un site ralentit aux heures de pointe.
Deux routes du serveur web à PHP
Nous exploitons les deux et choisissons selon les besoins du site. Dans les deux cas, la requête la plus rapide est celle à laquelle PHP n'a jamais à répondre.
LiteSpeed + LSAPI
- Visiteur
- LiteSpeed
- LSCachecache de pages
- LSAPI
- lsphp
LiteSpeed dialogue avec PHP par son propre protocole LSAPI et garde des pages entières dans LSCache, que WordPress et d'autres applications peuvent purger au moment exact où le contenu change.
Nginx + PHP-FPM
- Visiteur
- Nginx
- fastcgi_cachecache de pages
- FastCGI
- PHP-FPM
Nginx sert lui-même les fichiers statiques, transmet PHP à un pool PHP-FPM par un socket local, et peut garder les pages terminées dans son cache FastCGI pour les visiteurs non connectés.
-
Redis pour les objets et les sessions
Les réponses qu'une page redemande sans cesse à la base de données restent en mémoire, et les sessions ne touchent plus le disque.
-
Un cache de pages complètes
Un visiteur non connecté reçoit une page terminée sans que PHP ni la base de données ne soient sollicités. Paniers, comptes et paiements en sont exclus.
-
HTTP/2 et compression
Une seule connexion chiffrée transporte tous les fichiers de la page, le texte est compressé avant de partir, et les fichiers statiques ont de longues durées de cache avec des noms versionnés.
-
Temps passé en base de données
Le journal des requêtes lentes et EXPLAIN montrent quelle requête attend et pourquoi ; le plus souvent, un index ou une requête répétée en fait l'essentiel.
Comment nous optimisons les bases de données
Nous mesurons d'abord, puis changeons une seule chose
Chaque changement se fait par rapport à une mesure prise avant, pour vous montrer ce qu'il a apporté. Voici ces mesures.
opcache_get_status()Taux de succès, mémoire libre, et si des scripts sont évincés.pm.status_pathLa file d'attente et la fréquence à laquelle le pool a atteint sa limite.slowlogLa pile d'appels de chaque requête qui a duré trop longtemps.slow_query_log + EXPLAINQuelles requêtes attendent, combien de lignes elles lisent et quel index manque.redis-cli INFO statsLes succès du cache face aux échecs, et si des clés sont évincées.curl -w %{time_starttransfer}Le temps jusqu'au premier octet, mesuré de la même façon avant et après.
Vous voulez l'autre moitié du chiffre - la distance entre nos serveurs et vous ? Lancer le test de vitesse

Dites-nous quelle page est lente
Envoyez-nous l'adresse et l'endroit où elle tourne aujourd'hui. Nous examinons d'abord le serveur, puis vous disons ce que nous changerions et ce que cela devrait apporter - et chiffrons le travail après cet échange.