Sur un serveur dédié ou une machine virtuelle, PHP ne s'exécute plus dans le serveur web mais dans un gestionnaire de processus distinct, qui maintient un ensemble de travailleurs prêts à traiter les requêtes. Sa configuration par défaut convient rarement, et ses valeurs sont souvent copiées depuis un tutoriel écrit pour une machine sans rapport avec la vôtre, avec une mémoire, un trafic et une application différents. La configuration de PHP-FPM se réduit pourtant à quelques décisions, dont la principale consiste à ne pas autoriser plus de processus simultanés que la mémoire disponible ne le permet. Un serveur mal réglé de ce point de vue ne ralentit pas progressivement : il tombe d'un coup, au moment précis où il reçoit un peu plus de trafic que d'habitude, c'est à dire le jour où le site marche.

Comprendre ce que le gestionnaire fait

Quelques notions suffisent, et les connaître évite de copier des réglages au hasard sur un forum. Ce sujet complète les questions d'exploitation abordées dans la rubrique création de site Internet sur les projets hébergés en propre.

Un maître et des enfants

Un processus maître écoute les demandes transmises par le serveur web et les distribue à des processus enfants, chacun traitant une requête à la fois du début à la fin. Le nombre d'enfants disponibles détermine donc le nombre de requêtes PHP simultanées que la machine peut traiter. Toute requête supplémentaire attend en file, et l'utilisateur voit un temps de réponse qui s'allonge sans que le processeur soit saturé, ce qui déroute au diagnostic, la machine paraissant au repos alors que les visiteurs attendent.

Les groupes, un par site

Un même serveur peut faire tourner plusieurs groupes indépendants, chacun avec son propre utilisateur, ses propres réglages et son propre nombre d'enfants. C'est la bonne pratique dès qu'un serveur héberge plusieurs sites : un site qui sature son groupe ne prive pas les autres de ressources, et une compromission reste cantonnée à son utilisateur. Regrouper tous les sites dans un seul ensemble partagé est la configuration la plus fréquente et la moins souhaitable, et sa correction ne demande qu'un fichier de configuration par site.

Le point d'écoute

La communication entre le serveur web et le gestionnaire passe par une prise réseau locale ou par un fichier de communication local. Ce dernier est légèrement plus rapide et suffit quand les deux tournent sur la même machine, ce qui est le cas général. Les droits de ce fichier doivent permettre au serveur web de l'utiliser sans lui donner d'accès superflu, réglage souvent bâclé et source d'erreurs de connexion au démarrage, avec un message peu explicite qui envoie chercher ailleurs.

La différence avec les anciens modes

Ceux qui viennent d'un hébergement mutualisé ont l'habitude d'un PHP intégré au serveur web, où chaque connexion consomme de la mémoire même pour servir une image. Le gestionnaire séparé change ce comportement : les ressources statiques ne consomment aucun processus PHP, et la mémoire est réservée aux requêtes qui exécutent réellement du code. C'est ce qui permet à une petite machine de servir un trafic significatif, à condition que le réglage suive et que le serveur web serve lui même les fichiers statiques.

Ce que le réglage ne corrige pas

Aucune configuration ne compense un code lent. Si une page met deux secondes à se générer, augmenter le nombre d'enfants permettra de traiter plus de requêtes lentes simultanément, jusqu'à saturer la mémoire, mais ne rendra aucune page plus rapide. Le réglage sert à utiliser correctement les ressources disponibles, pas à masquer un problème applicatif, et il vient donc après le travail sur les requêtes et le cache, jamais à leur place.

Le fichier de configuration et sa hiérarchie

Les réglages se répartissent entre plusieurs fichiers, et savoir lequel l'emporte évite beaucoup de temps perdu. Le fichier global du gestionnaire porte les réglages du processus maître, journaux et limites générales. Le fichier de chaque groupe porte le mode, le nombre d'enfants et l'utilisateur. Les directives propres au langage, mémoire par script, durée d'exécution, taille des envois, peuvent être posées à trois endroits différents : le fichier de configuration du langage, le fichier du groupe, et le code de l'application lui même. La règle est que le plus proche de l'exécution l'emporte, ce qui explique qu'une valeur modifiée dans le fichier principal reste sans effet parce qu'un fichier de groupe la redéfinit. Avant de chercher pourquoi un réglage ne s'applique pas, il faut donc afficher la valeur réellement vue par le langage à l'exécution, ce qui se fait en une ligne et tranche immédiatement.

Répartition des processus enfants selon la charge reçue par le serveur

Choisir le gestionnaire de processus

Trois modes existent, et le choix a des conséquences directes sur la mémoire consommée au repos et sur le comportement en pointe.

Le mode dynamique

C'est le mode par défaut et le plus adapté à un site à trafic modéré. Un nombre minimal d'enfants reste disponible en permanence, d'autres sont créés à la demande jusqu'à un plafond, et les enfants inactifs sont arrêtés au bout d'un moment. Il offre un bon compromis entre réactivité et consommation, à condition que le plafond soit calculé et non copié. C'est ce mode que nous retenons dans la quasi totalité des cas.

Le mode statique

Un nombre fixe d'enfants est créé au démarrage et ne varie jamais. Il supprime le coût de création des processus et donne un comportement parfaitement prévisible, ce qui convient à un serveur dédié à un seul site à trafic soutenu et régulier. Il réserve en revanche la mémoire en permanence, ce qui est du gaspillage sur une machine partagée ou sur un site dont l'activité varie fortement entre le jour et la nuit.

Le mode à la demande

Aucun enfant n'est maintenu, chacun est créé à l'arrivée d'une requête et arrêté après un délai d'inactivité. Ce mode ne consomme rien au repos, ce qui le rend intéressant pour un site très peu visité ou pour un environnement de préproduction. Il ajoute en contrepartie le temps de création du processus à la première requête, perceptible sur un site consulté par intermittence, où le premier visiteur de la matinée paie systématiquement ce délai.

Le critère de choix

Le mode dynamique convient dès que le site reçoit du trafic de manière continue, même modeste. Le mode à la demande se justifie pour les environnements secondaires et les sites dormants, dont on veut qu'ils ne consomment rien. Le mode statique se réserve aux serveurs mono site à charge stable, où la prévisibilité prime. Dans le doute, le mode dynamique correctement calculé est le meilleur choix par défaut, et il se change en une ligne le jour où l'usage montre autre chose.

Réglage Comment le déterminer Erreur fréquente
Nombre maximal d'enfants Mémoire disponible divisée par la mémoire par processus Valeur copiée d'un tutoriel
Enfants au démarrage Environ un quart du maximum Égal au maximum, mémoire gaspillée
Enfants inactifs conservés Une fourchette autour du trafic habituel Fourchette trop étroite, créations permanentes
Requêtes avant recyclage Quelques centaines à quelques milliers Illimité, fuites de mémoire cumulées
Durée maximale d'exécution Selon les traitements longs réels Trop élevée, processus bloqués
Journal des requêtes lentes Activé avec un seuil de deux secondes Désactivé, aucune visibilité

Les délais, qui protègent contre l'effondrement

Deux durées méritent une attention particulière parce qu'elles décident du comportement de la machine quand quelque chose va mal. La durée maximale d'exécution d'un script doit être ajustée aux traitements les plus longs réellement nécessaires, imports, générations de rapports, envois en masse, sans être excessive : une valeur trop élevée fait qu'un traitement bloqué immobilise un processus pendant des minutes, et quelques requêtes de ce type suffisent à saturer un petit ensemble. La solution consiste à conserver une valeur basse pour les requêtes web et à autoriser des durées plus longues aux exécutions en ligne de commande, qui ne passent pas par le gestionnaire et disposent de leur propre configuration. La seconde durée, celle après laquelle un processus jugé bloqué est arrêté d'autorité, sert de filet : elle libère les enfants immobilisés par un service externe qui ne répond plus, situation qui provoque autrement une indisponibilité complète alors que le serveur n'est absolument pas chargé, cas de figure particulièrement déroutant lors du diagnostic.

Calculer le nombre d'enfants

C'est le réglage décisif, et il se calcule plutôt qu'il ne se devine. La méthode tient en trois mesures et une simple division, elle prend un quart d'heure, et elle rejoint la démarche de mesure préalable décrite dans notre article sur la manière d'auditer la vitesse d'une boutique WooCommerce.

Mesurer la mémoire réellement consommée

La première mesure est la mémoire moyenne occupée par un processus enfant en fonctionnement, qu'il faut relever pendant que le site sert du trafic réel plutôt qu'au repos. Sur un site de contenu, l'ordre de grandeur se situe entre trente et quatre vingts mégaoctets ; sur une boutique avec de nombreuses extensions, il dépasse fréquemment cent cinquante. Il faut retenir une valeur haute plutôt que la moyenne, les pages les plus lourdes étant précisément celles qui coïncident avec les pointes de trafic. Le relevé se fait avec les outils système habituels, en observant la mémoire réellement occupée par chaque processus et non celle qu'il a réservée.

Déterminer la mémoire disponible

La mémoire totale de la machine n'est pas disponible pour PHP : il faut soustraire ce que consomment le système, le serveur web, la base de données et l'éventuel cache d'objets. Sur une machine de quatre gigaoctets hébergeant également une base de données, il est prudent de ne pas réserver plus de la moitié de la mémoire totale aux processus PHP, et de laisser le reste au système et aux autres services. Cette réserve n'est pas de la prudence excessive : un serveur qui épuise sa mémoire déclenche l'arrêt brutal de processus par le système, en général celui de la base de données, ce qui produit un incident bien plus grave qu'un ralentissement, et dont la trace se trouve dans les journaux du système et non dans ceux du site.

Faire la division, puis vérifier

Le plafond d'enfants est le quotient de la mémoire réservée par la mémoire par processus, arrondi vers le bas. Sur une machine de quatre gigaoctets avec deux gigaoctets réservés et cent mégaoctets par processus, cela donne vingt enfants. Ce chiffre paraît toujours bas à ceux qui espéraient accueillir plusieurs centaines de visiteurs simultanés, et c'est le moment de rappeler qu'un enfant traite une requête en quelques dizaines de millisecondes quand le site est correctement mis en cache : vingt enfants suffisent largement à un trafic quotidien de plusieurs dizaines de milliers de pages. Le nombre de visiteurs simultanés au sens humain n'a aucun rapport avec le nombre de requêtes simultanées au sens du serveur, un visiteur passant l'essentiel de son temps à lire.

Régler les valeurs dérivées

Les autres valeurs se déduisent du plafond. Le nombre d'enfants créés au démarrage se situe autour du quart, la fourchette d'enfants inactifs conservés autour de dix et trente pour cent, ce qui évite à la fois de maintenir trop de processus au repos et d'en créer en permanence lors des variations normales. Ces valeurs ont bien moins d'importance que le plafond et n'ont pas à être ajustées finement, ce qui fait gagner du temps sur un réglage où l'on est tenté de s'attarder.

Vérifier sous charge réelle

Le contrôle final consiste à observer le nombre d'enfants actifs pendant une période de trafic normal, et pendant une pointe. Si le plafond est atteint régulièrement, il faut soit augmenter la mémoire de la machine, soit réduire le temps de génération des pages, jamais augmenter le plafond au delà de ce que la mémoire permet, ce qui reviendrait à échanger un ralentissement contre une panne. Si le nombre d'enfants actifs ne dépasse jamais le quart du plafond, la machine est surdimensionnée, ce qui est un problème de coût plutôt que de fonctionnement, et une bonne occasion de descendre d'un cran dans la gamme de l'hébergeur.

Effet des réglages sur le temps de réponse d'un site PHP, base cent avant intervention
Configuration initiale par défaut
100
Cache d'opcodes correctement dimensionné
46
Plafond d'enfants calculé
41
Recyclage des processus activé
39
Journal des requêtes lentes exploité
22

Effet cumulé mesuré sur un serveur de taille modeste. La dernière ligne ne relève pas du réglage mais des corrections applicatives que le journal a permis d'identifier.

Surveiller et ajuster

Une configuration correcte se dégrade avec le temps, à mesure que le site s'alourdit et que le trafic évolue. Quelques indicateurs permettent de le voir venir, dans le même esprit que la supervision décrite dans notre article sur la manière de surveiller un WordPress en production.

Activer la page d'état

Le gestionnaire expose une page d'état donnant le nombre d'enfants actifs et inactifs, la longueur de la file d'attente, le nombre de fois où le plafond a été atteint et la durée de la plus longue attente. Ces valeurs sont exactement celles dont on a besoin, et cette page doit être activée puis restreinte aux adresses internes, son contenu renseignant utilement un attaquant sur la capacité du serveur. Le compteur de saturations est le plus parlant : il doit rester à zéro. Une valeur non nulle signifie que des visiteurs ont attendu, même si aucune erreur n'a été servie, et cette attente ne se voit dans aucun autre indicateur.

Activer le journal des requêtes lentes

Ce journal enregistre la trace d'exécution de toute requête dépassant un seuil, ce qui indique précisément quelle fonction du code prenait la main au moment du dépassement. C'est l'outil de diagnostic le plus utile du lot, et il est presque toujours désactivé. Un seuil de deux à trois secondes produit peu de lignes et attrape exactement ce qu'il faut regarder. Sa lecture hebdomadaire suffit, et les mêmes traces reviennent en général, ce qui désigne rapidement le traitement à corriger.

Recycler les processus

Un processus qui traite des milliers de requêtes finit par accumuler de la mémoire non libérée, notamment à cause d'extensions PHP mal écrites. Fixer un nombre de requêtes après lequel l'enfant est remplacé règle ce problème définitivement, sans aucun inconvénient perceptible pour les visiteurs. Quelques centaines à quelques milliers selon le site, la valeur exacte n'ayant pas d'importance tant qu'elle n'est pas illimitée. Le remplacement se fait sans interruption de service, le maître attendant la fin de la requête en cours avant d'arrêter l'enfant.

Ne pas oublier le cache d'opcodes

Le gain le plus important sur un site PHP ne vient pas du réglage du gestionnaire mais du cache d'opcodes, qui conserve en mémoire la version compilée des fichiers. Sa mémoire allouée doit être suffisante pour contenir tout le code du site, ce qui se vérifie sur sa page d'état : un cache saturé se vide en permanence et annule son propre bénéfice, tout en donnant l'illusion d'être actif. Sur un site avec de nombreuses extensions, la valeur par défaut est souvent insuffisante, et le porter à cent vingt huit ou deux cent cinquante six mégaoctets change le temps de réponse de manière immédiatement mesurable, souvent davantage que tous les autres réglages réunis. Ce réglage se vérifie ensuite dans les journaux, une valeur trop généreuse masquant simplement une fuite au lieu de la corriger, et la trace des dépassements successifs indiquant assez vite lequel des deux cas s'applique.