Optimiser PHP pour la production

Le guide ultime pour utiliser PHP au maximum de ses possibilités (PHP-FPM, OPCache, JIT)

PHP, c’est le langage le plus simple à déployer ! Tous les hébergeurs proposent un environnement PHP préconfiguré : on balance nos fichiers et, bien souvent, on ne se soucie de rien d’autre (on ne vérifie même pas la version utilisée).

C’est pratique et rapide, mais, en tant que développeurs, ça ne nous encourage pas à comprendre PHP en profondeur. C’est un peu comme un pilote de F1 qui n’a aucune idée du fonctionnement de son moteur : pas très rassurant !

Dans cet article, je vais partager ce que j’ai appris au fil des années. On va voir comment configurer PHP pour la prod, comprendre ce qui se passe sous le capot et en tirer les meilleures performances possible.

alt text

Cet article fait écho à une série d’articles sur comment configurer un VPS pour héberger une application Symfony.


C’est quoi PHP-FPM ?

Selon la doc officielle, PHP-FPM est un gestionnaire de processus FastCGI.

Ok, mais c’est quoi FastCGI 😂 ?

FastCGI est simplement un protocole qui permet au serveur web de discuter avec PHP.

OK, et pourquoi tu me racontes tout ça ?

Dans la préhistoire du Web, on servait les applis PHP de deux façons :

  • un nouveau processus PHP à chaque requête (pas top si tu commences à avoir du trafic)
  • mod_php dans Apache. PHP intégré au serveur web : un seul processus pour tout, donc ressources partagées et problème de stabilité ou de performance.

Avec l’évolution du web moderne, l’arrivée de framework (Symfony ❤️ & Laravel), PHP c’est progressivement structuré pour répondre à ces nouveaux besoins en performance.

Et paf, en 2004, PHP-FPM débarque et change la donne :

  • Pool de processus dédié: PHP-FPM gère ses propres workers, indépendants du serveur web.
  • Performances: Allocation fine des ressources, ton site tient la charge sans transpirer.
  • Indépendance: PHP-FPM tourne dans son propre processus.
  • Multi-utilisateurs (système): Parfait pour l’hébergement mutualisé, un pool par project, chacun chez soi.

Le fonctionnement de PHP-FPM pour les null

Si tu as tout suivi jusqu’ici, tu auras compris que désormais PHP fonctionne tout seul comme un grand sur ta machine. Ton serveur web joue le rôle d’un simple reverse proxy : il balance (presque) toutes les requêtes HTTP à PHP pour obtenir une réponse.

Dans l’article sur la config d’un VPS pour Symfony, je te présentais la configuration suivante pour une pool :

[friday-deploy]
...
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.process_idle_timeout = 10s

PHP-FPM propose trois modes de fonctionnement distinct :

  • static
  • dynamic
  • ondemand

Le mode Static

Le mode static permet de définir un nombre fixe de processus à maintenir actif. Ce mode est très stable dans la consommation de ressource car tous les processus sont toujours actif, il n’y a pas de création/destruction. C’est une configuration idéale pour les environnements avec une charge constante et prévisible.

pm = static
pm.max_children = 10 #Nombre maximum de processus à maintenir

Si le serveur n’a plus de processus disponible quand il reçoit une requête, elle est mise en attente jusqu’à ce qu’un process se libère.

Le mode Dynamic

Le mode dynamic est un mode beaucoup plus flexible que le mode static. Il permet de définir un nombre maximum de processus (comme en static) mais surtout, on peut définir un nombre de minimum et maximum de processus.

pm = dynamic
pm.max_children = 50          #Maximum absolu de processus
pm.start_servers = 8          #Processus au démarrage
pm.min_spare_servers = 5      #Minimum de processus inactifs
pm.max_spare_servers = 15     #Maximum de processus inactifs

Dans l’exemple ci-dessus :

  1. A l’initialisation, 8 processus sont créé (pm.start_servers)
  2. Notre serveur reçoit 5 requêtes qui sont traité immédiatement, il reste 3 processus actif dans la pool.
  3. 2 processus supplémentaire sont créé car nous devons avoir tout le temps 5 process en attente (pm.min_spare_servers).

Ce mode permet une gestion assez fine de l’utilisation des ressources. Il est très pratique si on veut pouvoir s’adapter à la charge tout en contrôlant le flux de ressources attribué.

Le mode OnDemand

Le mode ondemand est le plus économe en ressource car il ne crée aucun processus à l’avance. Tout est créé “à la volée”. C’est un mode idéal pour les environnements à faible trafic, micro-service ou développement.

pm = ondemand
pm.max_children = 20              #Maximum de processus simultanés
pm.process_idle_timeout = 60s     #Délai avant destruction (inactif)
pm.max_requests = 500             #Recyclage après X requêtes

Voici un petit schéma qui permet de visualiser le trajet d’une requête avec PHP-FPM :

alt text

💡 Ce qu’il faut retenir, c’est qu’avec PHP-FPM le temps de réponse d’une application sera fortement dépendant du nombre de processus disponibles. Pour une application en production, il faut bien choisir le mode et faire évoluer le nombre de processus en fonction de la charge.

C’est parfait, on vient de voir un gros morceau mais PHP en a encore sous le capot, on passe immédiatement à OPcache.

PHP qui dégaine plus vite que son ombre avec OPcache

OPcache, c’est une extension PHP qui améliore les performances en mettant en cache le code machine compilé (OPcode) à partir d’un script PHP. Un opcode est une instruction machine de bas niveau. Il indique au processeur quoi faire.

alt text

Pour mieux comprendre l’interêt d’OPcache, il faut ouvrir le capot pour comprendre le trajet d’un script PHP sur un serveur.

Voici ce qui se passe quand un script PHP est exécuté :

  1. L’interpréteur charge le script
  2. Le script est analysé pour créer un arbre syntaxique
  3. Cet arbre est converti en opcodes pour le Zend Engine
  4. Le Zend Engine exécute ces opcodes
  5. Génération du résultat

💡 Zend Engine est le moteur d’exécution de PHP, c’est lui qui compile ton code PHP en instructions et les exécute pour produire le résultat final.

En utilisant OPcache, ce sont directement les opcode qui sont mis en cache. Ça rend l’exécution d’un script beaucoup plus rapide car il n’y a plus besoin de passer les étapes 2 & 3 de l’exemple précédent.

Voici une configuration de base pour OPcache en production :

opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=256
opcache.max_accelerated_files=20000 #Nombre max de fichiers mis en cache
opcache.validate_timestamps=0  #Aucune vérification de modification (on utilise toujours le cache)
opcache.preload=/path/to/preload.php #Charge immédiatement les fichiers importants dans OPcache

💡 Astuce pour les Symfony bro: si tu t’es toujours demandé ce que c’était le fichier config/preload.php dans ton projet Symfony ? C’est un fichier qui te permet de charger immédiatement les classes critiques de ton framework préféré dans OPcache. Je te conseille de bien renseigner le opcache.preload dans ta config pour ne pas louper ce gain de perf !

JIT: PHP en mode super saiyan

alt text

JIT = Just In Time compilation, c’est une nouveauté qui a été introduite avec la release de PHP 8.0. En résumé, JIT, c’est une fonctionnalité qui s’appuie sur OPcache et qui va transformer le code PHP en code machine pendant l’exécution.

JIT va détecter les parties du code les plus fréquement utilisé et va les optimiser en priorité. Ce processus permet de compiler PHP en code machine directement exécuté par le processeur. On évite de passer par Zend et globalement on augmente les performances.

voici un exemple de configuration pour activer JIT :

#Active OPcache (OBLIGATOIRE pour JIT)
opcache.enable=1

#Active JIT avec buffer minimal
opcache.jit_buffer_size=50M
opcache.jit=tracing

Voici un tableau qui représente le benchmark de PHP avec et sans JIT source:

alt text

On constate immédiatement que le gain de performance n’est pas notable pour les applications web classiques. JIT va surtout être utile pour les traitements assez lourd (algo complexe, boucle, etc).

💡Personnellement, je ne l’utilise pas pour mes applications mais c’est important de savoir que ça existe !



Et voilà, tu es désormais un peu plus intime avec ce bon vieux PHP qui ne cesse de se bonifier avec l’âge ! N’hésite pas à consulter mes autres articles et à me retrouver sur les réseaux. 😃

++