PHP-FPM: pourquoi le mode dynamique sature sous forte charge
Le paradoxe de PHP-FPM est bien connu de celles et ceux qui passent leur temps à regarder des courbes de charge plutôt que des captures d’écran de tableaux de bord: le mode pm = dynamic est conçu pour s’adapter au trafic, mais c’est justement cette adaptation qui peut devenir coûteuse quand les requêtes arrivent par vagues.
Sous faible charge, le mécanisme paraît parfaitement raisonnable. PHP-FPM démarre quelques processus, en conserve un certain nombre en réserve, puis en crée de nouveaux lorsque le trafic monte. Quand la pression retombe, il en supprime une partie. Propre, souple, presque élégant. Puis arrive le pic de trafic — et le serveur passe une partie de son temps à gérer ses processus au lieu de répondre aux utilisateurs.
La comparaison PHP-FPM dynamique contre statique ne se résume donc pas à une question de préférence dans un fichier de configuration. Elle touche directement à la mémoire disponible, au temps de réponse, à la stabilité de la machine et à la manière dont l’application encaisse ses pointes de fréquentation.
Que fait réellement PHP-FPM en mode dynamique?
PHP-FPM ne traite pas les requêtes avec un seul processus PHP qui ferait tout, tout le temps. Il s’appuie sur un ensemble de processus enfants, généralement appelés travailleurs, auxquels le serveur web transmet les requêtes.
En mode dynamique, la taille de cet ensemble varie continuellement. Quatre directives structurent son comportement:
pm.max_childrenfixe le nombre maximal de processus PHP-FPM pouvant fonctionner simultanément;pm.start_serversdéfinit combien de processus sont créés au démarrage;pm.min_spare_serversindique le nombre minimal de processus inoccupés à conserver en réserve;pm.max_spare_serversfixe le nombre maximal de processus inoccupés que PHP-FPM peut garder.
Le principe est facile à comprendre. Au démarrage, PHP-FPM crée un nombre initial de processus. Si le trafic augmente et que les processus disponibles ne suffisent plus, il en crée d’autres, jusqu’à la limite définie par pm.max_children. Lorsque la demande baisse, il détruit les processus devenus inutiles afin de libérer de la mémoire.
Sur le papier, c’est un compromis très séduisant: on ne réserve pas toute la capacité maximale en permanence et on évite de faire tourner une armée de processus pendant les périodes creuses. Pour une application peu sollicitée ou installée sur une machine partagée, ce comportement peut être parfaitement adapté.
Le problème apparaît lorsque le trafic est irrégulier, avec des montées rapides et répétées. PHP-FPM doit alors créer des processus, les initialiser, leur faire charger le code de l’application, parfois les extensions nécessaires, puis les supprimer dès que la pression redescend. Cette gestion du cycle de vie consomme des cycles processeur. Et pendant ce temps, les requêtes continuent d’attendre.
En mode dynamique, le serveur ne manque pas forcément de puissance brute: il peut simplement la dépenser à créer et détruire les processus dont il aurait besoin pour répondre.
Ce phénomène ne signifie pas que pm = dynamic est mal conçu. Il signifie que son modèle de fonctionnement a un coût, particulièrement visible lorsque la charge est forte et change rapidement.
Pourquoi les pics de trafic mettent-ils le mode dynamique en difficulté?
Le mot important n’est pas seulement « forte charge ». C’est aussi « variation ».
Une application qui reçoit un trafic élevé mais régulier peut parfois fonctionner correctement avec un nombre de processus stable. À l’inverse, une application qui passe brutalement d’une activité modérée à une forte demande peut provoquer une succession de créations de processus. PHP-FPM essaie de suivre le mouvement, mais il ne peut pas faire apparaître instantanément des processus prêts à traiter les requêtes.
Chaque nouveau processus doit être créé puis préparé pour exécuter le code PHP. Selon l’application, cela peut impliquer le chargement du cadre applicatif, de nombreuses bibliothèques, des fichiers de configuration, des extensions et parfois une quantité non négligeable de données en mémoire. Un petit service PHP ne se comporte pas comme un système de gestion de contenu chargé de modules, de connecteurs et de logique métier accumulés au fil des années — la fameuse application qui devait rester simple.
Plus le processus est lourd, plus le coût de sa création est sensible. L’empreinte mémoire moyenne d’un processus PHP-FPM peut se situer autour de 15 Mo pour une application légère, mais dépasser largement 100 Mo selon la complexité du code et de l’environnement. Cette fourchette est suffisamment large pour rendre les réglages génériques presque inutiles.
Imaginons un trafic qui augmente rapidement. Les processus existants sont occupés. PHP-FPM en crée de nouveaux, mais ces processus ne sont pas immédiatement disponibles. Si le trafic continue de monter, la file d’attente se remplit. Le serveur web, souvent Nginx, transmet les requêtes au rythme que PHP-FPM peut absorber. Lorsque la file atteint sa limite et qu’aucun processus ne se libère assez vite, les erreurs apparaissent:
502 Bad Gateway, lorsque le serveur web n’obtient pas une réponse valide du service PHP;503 Service Unavailable, lorsque le service n’est pas en mesure de traiter la demande.
Ces erreurs ne prouvent pas à elles seules que le mode dynamique est responsable. Une base de données lente, une dépendance externe indisponible, une saturation réseau ou un code applicatif trop coûteux peuvent produire des symptômes proches. Mais une file d’attente qui se remplit pendant que PHP-FPM crée et détruit continuellement des processus constitue un signal très sérieux.
Le coût caché du redimensionnement
Le mode dynamique présente deux coûts distincts qu’on mélange souvent.
Le premier est le coût processeur lié à la création et à la destruction répétées des processus. C’est le point faible le plus évident sous forte charge.
Le second est le coût en latence. Même si la consommation processeur reste acceptable, une requête peut attendre qu’un processus soit disponible ou qu’un nouveau processus soit créé. Cette attente s’ajoute au temps d’exécution réel de l’application. Dans les graphiques, on observe alors des temps de réponse qui s’allongent sans que le code PHP ait nécessairement changé.
Il faut aussi regarder la forme du trafic. Une hausse progressive laisse à PHP-FPM le temps de compléter son groupe de processus. Une avalanche de requêtes courtes, concentrées sur quelques secondes, est plus difficile à absorber. Le serveur doit fournir une capacité immédiatement disponible; il ne peut pas compter uniquement sur un mécanisme d’adaptation qui prend du temps.
Le mode statique supprime-t-il vraiment la surcharge?
En mode pm = static, PHP-FPM crée dès le démarrage le nombre de processus indiqué par pm.max_children. La capacité maximale est donc préparée à l’avance. PHP-FPM n’a plus besoin de faire varier en permanence la taille du groupe en fonction du trafic.
Cette approche supprime le coût de création et de destruction lié aux fluctuations. Lorsqu’un pic survient, les processus sont déjà là, prêts à recevoir des requêtes. On gagne en prévisibilité et on évite la latence de démarrage qui accompagne le redimensionnement du groupe.
C’est la raison pour laquelle le mode statique peut être particulièrement intéressant sur une machine dédiée à une application PHP qui reçoit un trafic soutenu. Si la charge est connue et que la mémoire a été correctement dimensionnée, conserver un nombre fixe de processus donne à PHP-FPM un comportement beaucoup plus stable.
Mais le mode statique n’est pas un bouton magique. Il échange une surcharge de gestion des processus contre une occupation mémoire permanente.
| Paramètre | Mode dynamique | Mode statique |
|---|---|---|
| Nombre de processus | Variable, entre le démarrage et pm.max_children | Fixé à pm.max_children dès le démarrage |
| Réaction à un pic soudain | Peut nécessiter la création de nouveaux processus | Les processus sont déjà disponibles |
| Coût processeur | Plus élevé lorsque PHP-FPM crée et détruit souvent des processus | Pas de redimensionnement permanent du groupe |
| Occupation mémoire | Évolue avec le nombre de processus actifs et réservés | Réservée en permanence pour le nombre configuré |
| Risque principal | Latence, file d’attente et erreurs sous forte variation | Saturation mémoire et déclenchement de l’OOM Killer |
| Contexte favorable | Trafic variable, machine partagée, capacité mémoire limitée | Machine dédiée, trafic régulier, mémoire correctement calculée |
Le mode statique peut donc améliorer la stabilité d’une application, mais seulement si pm.max_children correspond à la mémoire réellement disponible. Configurer une valeur élevée parce que la machine possède beaucoup de mémoire sur le papier est une excellente manière de découvrir l’OOM Killer au pire moment.
Comment calculer pm.max_children sans jouer à la roulette?
La règle de base tient en une formule:
pm.max_children = mémoire disponible pour PHP-FPM / taille moyenne d’un processus PHP
La difficulté n’est pas la division. Elle se trouve dans les deux valeurs.
La mémoire totale de la machine n’est pas entièrement disponible pour PHP-FPM. Le système d’exploitation, le serveur web, la base de données, les services de supervision, les caches et les autres composants ont eux aussi besoin de fonctionner. Si PHP-FPM partage la machine avec MariaDB ou PostgreSQL, lui attribuer toute la mémoire restante sur un coin de feuille est un plan assez direct pour créer une panne en cascade.
Il faut donc partir d’une enveloppe réaliste dédiée à PHP-FPM, puis mesurer l’empreinte des processus dans des conditions représentatives. La mémoire d’un processus dépend de l’application chargée, du cadre utilisé, des extensions, du volume de code exécuté et parfois du type de requête. Une route légère et une route qui génère un catalogue complexe ne consomment pas forcément la même chose.
Prenons une machine qui peut consacrer 2 Go à PHP-FPM. Si un processus utilise en moyenne 50 Mo, une division brute donne environ 40 processus. Cette valeur n’est pas automatiquement la bonne configuration: elle ne laisse aucune marge aux variations, aux autres services ou aux pointes de consommation. La formule sert à poser une limite cohérente, pas à justifier un réglage au chiffre près.
Dans la pratique, on cherchera à observer:
- la mémoire réellement utilisée par les processus PHP-FPM lorsque l’application traite ses requêtes habituelles;
- la différence entre la consommation au repos et celle observée pendant une opération lourde;
- la mémoire disponible pour les services voisins;
- l’évolution de la consommation lorsque le nombre de requêtes simultanées augmente;
- la présence éventuelle d’un processus qui grossit progressivement au fil des requêtes.
Le dernier point mène à pm.max_requests. Cette directive permet de détruire puis de remplacer un processus après un nombre défini de requêtes traitées. Elle sert notamment à limiter l’accumulation progressive de fuites mémoire. Le remplacement n’est pas gratuit, mais il peut empêcher un processus dégradé de conserver sa mémoire indéfiniment.
On doit toutefois éviter de traiter pm.max_requests comme une réparation du code. Si une extension ou une partie de l’application fuit, recycler les processus masque le symptôme et maintient le service en état de marche. C’est utile en exploitation, mais cela ne dispense pas de chercher la cause.
Que faut-il mesurer avant de changer de mode?
Passer de dynamique à statique sans observer le serveur revient à changer le comportement d’un moteur en se fiant uniquement au bruit du capot. Les bons indicateurs ne sont pas tous dans PHP-FPM, et aucun ne raconte l’histoire à lui seul.
La file d’attente d’écoute
La file d’attente indique que des requêtes attendent un processus disponible. Une file qui reste régulièrement élevée signifie que PHP-FPM ne suit pas le rythme d’arrivée des requêtes.
Il faut distinguer deux situations:
1. la file augmente parce que les processus exécutent des traitements réellement longs;
2. la file augmente parce que PHP-FPM passe son temps à créer de nouveaux processus.
Dans le premier cas, le mode statique ne suffira pas nécessairement. Si chaque requête attend une base de données lente ou un service tiers, disposer de davantage de processus peut même amplifier la pression sur la dépendance en question.
Dans le second cas, la suppression du redimensionnement permanent peut réduire une partie de la latence et de la consommation processeur. Encore faut-il que la mémoire suive.
Le nombre de processus actifs et disponibles
Les valeurs liées à pm.max_children, aux processus actifs et aux processus inoccupés permettent de voir si le groupe atteint souvent sa limite. Si pm.max_children est constamment atteint, PHP-FPM ne dispose pas de capacité supplémentaire. Mais cela ne veut pas dire qu’il faut simplement augmenter cette valeur.
Chaque processus supplémentaire consomme de la mémoire et peut ouvrir davantage de connexions vers les services en aval. Le serveur PHP peut alors sembler respirer tandis que la base de données, elle, commence à suffoquer. L’optimisation d’un bassin de processus ne se fait jamais en ignorant le reste de la chaîne.
La mémoire et l’OOM Killer
La mémoire disponible doit être suivie pendant les pics, pas uniquement lorsque le serveur est tranquille. Une machine qui semble confortable à trois heures du matin peut manquer de mémoire dès que PHP-FPM, la base de données et le système de cache travaillent simultanément.
Le déclenchement de l’OOM Killer est un événement particulièrement brutal: le noyau choisit des processus à terminer pour récupérer de la mémoire. Une configuration statique trop ambitieuse peut donc transformer un problème de latence en indisponibilité franche.
C’est toute l’ironie du réglage: vouloir éviter quelques processus créés à la volée peut conduire à supprimer des processus qui fonctionnaient très bien.
Le processeur
En mode dynamique, une forte activité processeur accompagnée de nombreuses créations et destructions de processus est un indice de surcharge de gestion. Si le processeur est déjà saturé par le code applicatif, les requêtes SQL ou le chiffrement, augmenter le nombre de processus ne fera pas apparaître de capacité supplémentaire.
Le processeur doit être observé avec le temps de réponse, la file d’attente et la mémoire. Isolé, son pourcentage d’utilisation raconte rarement toute la vérité.
Le bon réglage n’est pas celui qui affiche le plus grand nombre de processus. C’est celui qui absorbe le trafic sans transformer la mémoire, la base de données ou la file d’attente en goulot d’étranglement.
Le mode dynamique est-il toujours le mauvais choix?
Non. Et c’est ici que les débats de communauté deviennent parfois un peu trop binaires: dynamique contre statique, comme si chaque serveur devait choisir son camp une fois pour toutes.
Le mode dynamique reste pertinent lorsque le trafic varie fortement, que la machine héberge plusieurs services ou que la mémoire disponible est limitée. Dans ce contexte, réserver en permanence un grand nombre de processus peut coûter plus cher que le redimensionnement lui-même.
Il peut aussi convenir lorsque les pics sont rares et courts, ou lorsque l’application consomme beaucoup de mémoire par processus. Avec une empreinte de 100 Mo ou davantage, pré-allouer plusieurs dizaines de processus devient rapidement une décision lourde. La souplesse du mode dynamique peut alors préserver une marge nécessaire au reste du système.
Le vrai problème apparaît quand la configuration dynamique est laissée avec des valeurs par défaut alors que l’application, elle, a changé. Le code s’est épaissi, le trafic a progressé, une nouvelle intégration a ajouté des appels externes, mais le bassin PHP-FPM fonctionne encore comme au premier jour. Le serveur ne fait pas de commentaire sur cette situation: il attend, il crée des processus, il manque de mémoire, puis il renvoie des erreurs.
Il existe également le mode pm = ondemand, dans lequel les processus sont créés lorsqu’une requête arrive et détruits après une période d’inactivité. Ce fonctionnement peut limiter l’occupation mémoire lorsque le trafic est faible, mais il introduit davantage de création à la demande. Pour une application régulièrement sollicitée, il ne constitue pas automatiquement une solution aux pics de charge.
Le choix dépend donc de la forme du trafic et du budget mémoire, pas d’une règle universelle.
Comment réduire la pression sans seulement augmenter pm.max_children?
Augmenter la limite est parfois nécessaire, mais ce devrait être une conclusion tirée de mesures, pas un réflexe. Avant de pousser le curseur, on peut agir sur plusieurs niveaux.
Réduire le temps de traitement des requêtes
Un processus PHP occupé pendant une longue durée ne peut pas traiter une autre requête. Si l’application passe du temps à attendre une base de données, une API distante ou un fichier, la capacité effective du groupe diminue.
La réduction du temps de traitement peut passer par une meilleure indexation SQL, une mise en cache adaptée, la suppression d’appels inutiles ou la réécriture d’un parcours applicatif trop bavard. Ce travail est moins spectaculaire qu’un changement de valeur dans php-fpm.conf, mais il produit souvent un résultat plus durable.
Distinguer les requêtes web des traitements lourds
Une application qui exécute des exports, des traitements d’images ou des tâches de synchronisation dans les mêmes processus que les requêtes web se condamne à partager une ressource rare. Les traitements longs occupent les processus et font patienter les visiteurs.
Séparer ces usages — par une file de tâches, un service dédié ou un traitement différé — permet de préserver la capacité de réponse du site. Le mode statique devient alors plus simple à dimensionner, car les processus web traitent des requêtes dont la durée est plus homogène.
Éviter de confondre capacité et concurrence
pm.max_children définit le nombre maximal de requêtes PHP traitées simultanément. Il ne rend pas chaque requête plus rapide et n’augmente pas automatiquement la capacité de la base de données.
Si l’application est limitée par une dépendance externe, lancer davantage de traitements en parallèle peut aggraver la situation. On obtient alors davantage de processus PHP occupés, davantage de connexions ouvertes et une file d’attente qui se déplace simplement ailleurs.
Stabiliser le groupe de processus
Lorsque les mesures montrent que le trafic nécessite presque toujours le même nombre de processus, le mode statique peut réduire le travail de gestion du groupe. On échange une adaptation permanente contre une capacité préparée.
Cette décision a du sens sur une machine dédiée ou clairement réservée à l’application. Sur une machine mutualisée, elle demande davantage de prudence: les processus pré-alloués restent présents même lorsque le trafic baisse, et la mémoire n’est plus disponible pour les autres services.
Quels réglages examiner dans une configuration PHP-FPM?
Les directives ne doivent pas être lues séparément. Elles forment un ensemble.
Avec pm = dynamic, pm.start_servers détermine la capacité disponible au démarrage. Une valeur trop faible peut rendre les premières montées en charge plus sensibles à la création de processus. pm.min_spare_servers et pm.max_spare_servers encadrent le nombre de processus gardés en réserve. Ces valeurs influencent la rapidité avec laquelle PHP-FPM peut répondre à une hausse de trafic sans créer immédiatement de nouveaux processus.
pm.max_children, lui, reste la limite principale. Une valeur trop basse provoque une file d’attente persistante. Une valeur trop haute expose la machine à la saturation mémoire. La configuration correcte se trouve entre ces deux échecs, avec une marge suffisante pour les variations réelles de l’application.
pm.max_requests intervient sur la durée de vie des processus. Il peut contribuer à contenir une consommation mémoire qui augmente progressivement, mais il crée aussi un renouvellement régulier. Là encore, le réglage doit être observé: remplacer des processus trop fréquemment peut recréer une partie du coût que l’on cherchait précisément à éviter.
Pour relier les réglages aux symptômes, on peut retenir cette lecture:
1. La file d’attente augmente, mais la mémoire est disponible.
La limite pm.max_children est peut-être trop basse, ou les requêtes sont trop longues. Avant de l’augmenter, on cherche ce qui retient les processus.
2. Le processeur grimpe pendant les pics et les processus changent sans cesse.
Le coût de gestion du mode dynamique devient probablement visible. Un groupe plus stable peut réduire cette surcharge.
3. La mémoire sature lorsque le nombre de processus augmente.
Le problème n’est pas de créer encore plus de processus. Il faut recalculer la capacité à partir de leur empreinte réelle et préserver les services voisins.
4. Des erreurs 502 ou 503 apparaissent lorsque la file est pleine.
PHP-FPM n’absorbe plus la demande au rythme attendu. Il faut corréler ces erreurs avec les journaux, la durée des requêtes, la mémoire et l’état du serveur web.
5. La mémoire d’un processus augmente progressivement.
pm.max_requests peut limiter l’accumulation, mais le code ou l’extension responsable doit rester dans le viseur.
Cette méthode est moins glamour qu’un réglage présenté comme un game changer. Elle a toutefois un avantage rare dans l’informatique: elle évite de régler un problème pour en fabriquer deux autres.
Une architecture moderne change-t-elle la décision?
Oui, parce que PHP-FPM ne vit pas dans le vide.
Dans une architecture composée de plusieurs services, chaque processus PHP peut dépendre d’une base de données distante, d’un service d’identité, d’une file de messages ou d’une API interne. Le nombre de processus PHP devient alors aussi un facteur de concurrence pour ces composants.
Un réglage statique généreux peut améliorer la disponibilité du frontal tout en surchargeant le service appelé derrière. À l’inverse, une limite trop prudente peut laisser des ressources inutilisées sur une machine dédiée et allonger inutilement la file d’attente.
Les applications modernes ne suppriment pas ce problème; elles le rendent plus visible. Le découpage en services, les conteneurs et l’intégration continue apportent de la souplesse, mais ils ne remplacent pas le dimensionnement. Un conteneur PHP qui redémarre parce qu’il dépasse sa limite mémoire n’est pas plus élégant qu’un ancien serveur monolithique qui tombe en panne. C’est simplement une panne avec de meilleurs fichiers de configuration.
La supervision doit donc suivre la chaîne complète:
- temps d’attente dans la file PHP-FPM;
- nombre de processus actifs et disponibles;
- limite atteinte par
pm.max_children; - mémoire consommée par les processus;
- activité processeur lors des créations et destructions;
- temps de réponse applicatif;
- erreurs
502et503; - temps passé dans la base de données et les services externes.
C’est cette corrélation qui permet de savoir si le mode dynamique est réellement le facteur limitant ou s’il ne fait que révéler une faiblesse située ailleurs.
Alors, dynamique ou statique?
Pour une application à trafic variable, hébergée sur une machine aux ressources partagées ou limitées, le mode dynamique conserve une vraie logique. Il évite de mobiliser en permanence une grande quantité de mémoire et laisse PHP-FPM adapter sa capacité.
Pour une application fortement sollicitée, installée sur une machine dédiée et dont l’empreinte mémoire est bien connue, le mode statique peut apporter une réponse plus stable. Les processus sont disponibles avant le pic, le coût de redimensionnement disparaît et la latence devient plus prévisible.
Mais cette décision doit se prendre avec des mesures. Le nombre de processus acceptable dépend de leur mémoire moyenne, de la mémoire réservée aux autres services et de la durée réelle des requêtes. La configuration PHP-FPM n’est pas un talisman que l’on copie d’un serveur à l’autre. Deux applications PHP peuvent avoir des comportements radicalement différents sous le capot.
Le mode dynamique sature sous forte charge lorsqu’il doit adapter trop souvent la taille du groupe alors que les requêtes arrivent plus vite que les processus ne peuvent être préparés. Le mode statique évite ce coût, mais il réserve en permanence la mémoire correspondante. Voilà le compromis, sans poudre aux yeux et sans guerre de religion entre fichiers de configuration.
Dans les équipes PHP, on gagnerait à regarder moins souvent la directive isolée et davantage le système complet: trafic, durée des requêtes, mémoire, base de données, file d’attente et erreurs côté serveur web. C’est là que se trouve le vrai réglage.
Et la suite? Avec des applications toujours plus distribuées, des déploiements conteneurisés et des pics de trafic moins prévisibles, le débat ne va pas disparaître. Il va simplement devenir plus fin. À nous de continuer à mesurer avant de suivre la prochaine mode — même lorsqu’elle est présentée comme la solution miracle du moment.




