...

Configurer correctement la gestion des processus PHP-FPM : pm.max_children & Co. expliqué

Réglage php-fpm détermine le nombre de processus PHP-FPM pouvant être exécutés simultanément, la vitesse à laquelle les nouveaux processus démarrent et la durée pendant laquelle ils traitent les requêtes. Je vais vous montrer comment pm.max_children, pm, pm.start_servers, pm.min_spare_servers, pm.max_spare_servers et pm.max_requests de manière à ce que votre application réagisse rapidement sous la charge et que le serveur ne bascule pas en mode swapping.

Points centraux

  • mode pm: choisissez correctement entre static, dynamic ou ondemand afin que les processus soient adaptés à votre trafic.
  • pm.max_children: aligner le nombre de processus PHP simultanés sur la RAM et la consommation réelle du processus.
  • Valeurs de démarrage/de réserve: pm.start_servers, pm.min_spare_servers, pm.max_spare_servers équilibrer de manière judicieuse.
  • recyclage: Atténuer les fuites de mémoire avec pm.max_requests sans générer de surcharge inutile.
  • Suivi: surveiller les journaux, l'état et la RAM, puis procéder à des ajustements progressifs.

Pourquoi la gestion des processus est importante

Je participe PHP-FPM l'exécution de chaque script PHP comme un processus distinct, et chaque requête parallèle nécessite son propre worker. Sans limites appropriées, les requêtes se bloquent dans les files d'attente, ce qui entraîne Timeouts et des erreurs. Si je fixe des limites trop élevées, le pool de processus consomme toute la mémoire vive et le noyau commence à échanger. Cet équilibre n'est pas une question de hasard : je m'appuie sur des valeurs réelles et je garde une marge de sécurité. Ainsi, la latence reste faible et le débit stable, même lorsque la charge varie.

Ce qui m'importe, c'est une communication claire. valeur cible: Combien d'exécutions PHP simultanées est-ce que je veux permettre sans épuiser la RAM ? En même temps, je vérifie si les goulots d'étranglement se produisent plutôt dans la Base de données, dans les API externes ou dans le serveur web. Ce n'est qu'une fois que j'ai identifié le goulot d'étranglement que je choisis les valeurs appropriées pour pm, pm.max_children et autres. Je commence de manière conservatrice, puis je mesure et augmente progressivement. Cela me permet d'éviter les redémarrages brutaux et les pannes inattendues.

Les trois modes pm : static, dynamic, ondemand

Le mode static tient toujours exactement pm.max_children processus prêts. Cela fournit des latences très prévisibles, car aucun processus de démarrage n'est nécessaire. J'utilise static lorsque la charge est très régulière et que suffisamment de RAM est disponible. Cependant, lorsque la demande varie, je gaspille facilement dans static. Mémoire. C'est pourquoi j'utilise static de manière ciblée là où j'ai besoin d'une exécution constante.

Avec dynamique Je lance une quantité initiale et laisse la taille du pool osciller entre min_spare et max_spare. Ce mode convient au trafic par vagues, car les workers sont créés et supprimés selon les besoins. Je garde toujours suffisamment de processus inactifs pour absorber les pics sans temps d'attente. Cependant, un trop grand nombre de workers inactifs mobilise inutilement des ressources. RAM, c'est pourquoi je gère la marge de sécurité de manière stricte. Ainsi, la piscine reste mobile sans gonfler.

En mode ondemand Au départ, il n'y a pas de workers, PHP-FPM ne les démarre qu'en cas de requêtes. Cela permet d'économiser de la mémoire pendant les phases de repos, mais le premier accès entraîne une légère latence. Je choisis ondemand pour les pools rarement appelés, les outils d'administration ou les points de terminaison cron. Pour les sites web très fréquentés, ondemand offre généralement des temps de réponse moins bons. Dans ce cas, je préfère clairement dynamic avec des valeurs de réserve correctement définies.

Dimensionner correctement pm.max_children

Je calcule pm.max_children à partir de la RAM disponible pour PHP et de la mémoire moyenne par worker. Pour cela, je réserve d'abord de la mémoire pour le système, le serveur web, la base de données et les caches afin que le système ne tombe pas en panne. Externalisation fonctionne. Je divise la RAM restante par la consommation réelle mesurée du processus. À partir de la théorie, je retire une marge de sécurité de 20 à 30 % afin de compenser les valeurs aberrantes et les pics de charge. J'utilise le résultat comme valeur de départ, puis j'observe l'effet.

Je détermine la consommation moyenne du processus à l'aide d'outils tels que ps, top ou htop et je regarde RSS/RES. Important : je mesure sous une charge typique, pas au ralenti. Si je charge beaucoup de plugins, de frameworks ou de grandes bibliothèques, la consommation par worker grimpe sensiblement. De plus, le CPU limite la courbe : plus de processus n'aident pas si un Fil de discussion unique-Puissance du CPU limitée par requête. Si vous souhaitez approfondir vos connaissances sur les caractéristiques du CPU, vous trouverez des informations générales sur Performances mono-thread.

Je garde mes hypothèses transparentes : quelle quantité de RAM est réellement disponible pour PHP ? Quelle est la taille d'un worker pour des requêtes typiques ? Quels sont les pics qui se produisent ? Si les réponses sont correctes, je définis pm.max_children, je procède à un rechargement en douceur et je vérifie la RAM, les temps de réponse et les taux d'erreur. Ce n'est qu'ensuite que je continue à augmenter ou à diminuer petit à petit.

Valeurs indicatives en fonction de la taille du serveur

Le tableau suivant me donne valeurs initiales Elle ne remplace pas les mesures, mais fournit des indications fiables pour les premiers réglages. J'adapte les valeurs à chaque application et les vérifie à l'aide d'un système de surveillance. Si des réserves restent inutilisées, j'augmente prudemment les valeurs. Si le serveur atteint la limite de la mémoire vive, je réduis les valeurs.

Mémoire vive du serveur RAM pour PHP Ø Mo/travailleur pm.max_children (Début) Utilisation
1 à 2 Go ~1 Go 50-60 15–20 Petits sites, blogs
4 à 8 Go ~4 à 6 Go 60-80 30–80 Commerce, petites boutiques
16+ Go ~10–12 Go 70-90 100–160 Charge élevée, API, boutiques

Je lis le tableau de droite à gauche : Est-ce que le Utilisation Pour le projet, je vérifie si la RAM est réservée de manière réaliste pour PHP. Ensuite, je choisis une taille de worker adaptée à la base de code et aux extensions. Puis, je définis pm.max_children et observe l'effet en fonctionnement réel. Le taux de réussite et la stabilité augmentent lorsque je documente clairement ces étapes.

Définir les valeurs de démarrage, de réserve et de demande

Avec pm.start_servers Je détermine le nombre de processus immédiatement disponibles. Une valeur trop faible génère des démarrages à froid sous charge, une valeur trop élevée occupe inutilement de la mémoire vive. Je me base souvent sur 15 à 30 % de pm.max_children et j'arrondis lorsque la charge démarre plutôt calmement. En cas de pics de trafic, je choisis une quantité de démarrage légèrement plus élevée afin que les requêtes ne s'accumulent pas avant qu'un nombre suffisant de workers ne soit disponible. Ce réglage fin réduit considérablement le temps de réponse initial.

Les valeurs pm.min_spare_servers et pm.max_spare_servers définissent la plage d'inactivité. Je garde suffisamment de travailleurs libres pour que les nouvelles demandes puissent être traitées immédiatement, mais pas trop pour éviter que les processus inactifs ne gaspillent de la mémoire. Pour les boutiques, je préfère définir une plage plus étroite afin de lisser les pics. Avec pm.max_requests Je recycle les processus après quelques centaines de requêtes afin de limiter la dérive de mémoire. Pour les applications discrètes, je choisis 500 à 800, en cas de suspicion de fuites, je choisis délibérément une valeur inférieure.

Monitoring et recherche d'erreurs

Je vérifie régulièrement Logs, pages d'état et RAM. Les avertissements concernant les limites pm.max_children atteintes sont pour moi un signal clair qu'il faut augmenter la limite supérieure ou optimiser le code/la base de données. Si les erreurs 502/504 s'accumulent, je consulte les journaux du serveur web et les files d'attente. Des fluctuations importantes de la latence indiquent un nombre insuffisant de processus, des E/S bloquantes ou des coûts de processus trop élevés. Je commence par examiner les faits concrets, puis je réagis par petites étapes, jamais par des sauts XXL.

Je détecte plus rapidement les goulots d'étranglement lorsque je Temps d'attente Je mesure tout au long de la chaîne : serveur web, PHP-FPM, base de données, services externes. Si le temps de traitement backend augmente uniquement pour certaines routes, j'isole les causes à l'aide du profilage. Si les temps d'attente se produisent partout, je commence par la taille du serveur et du pool. Il est également utile d'examiner les files d'attente des travailleurs et les processus en statut D. Ce n'est qu'une fois que j'ai compris la situation que je modifie les limites – et je documente soigneusement chaque modification.

Interaction entre le serveur web et PHP-FPM

Je veille à ce que Serveur webLes limites et PHP-FPM fonctionnent en harmonie. Un nombre trop élevé de connexions simultanées au serveur web avec trop peu de workers entraîne des files d'attente et des délais d'attente. Si le nombre de workers est élevé, mais que le serveur web limite l'acceptation, les performances restent faibles. Des paramètres tels que worker_connections, event-Loop et Keep-Alive ont un effet direct sur la charge PHP. Des conseils pratiques pour un réglage précis sont fournis dans la section Pools de threads dans le serveur web.

Je garde Keep-Alive-fenêtre temporelle afin que les connexions inactives ne bloquent pas inutilement les travailleurs. Pour les ressources statiques, je privilégie une mise en cache agressive avant PHP afin d'éviter de surcharger le pool. Les caches proxy inversés sont également utiles lorsque des réponses identiques sont fréquemment consultées. Cela me permet de maintenir pm.max_children à un niveau bas tout en accélérant la livraison. Réduire la charge de travail par requête est souvent la solution la plus efficace.

Réglages fins dans php-fpm.conf

Je vais au-delà des valeurs fondamentales et j'ajuste les Paramètres de la piscine bien. Avec pm.max_spawn_rate je limite la vitesse à laquelle de nouveaux workers peuvent être créés afin que le serveur ne lance pas de processus de manière trop agressive lors des pics de charge et ne tombe pas dans le CPU thrashing. Pour ondemand, je définis avec pm.process_idle_timeout déterminé à quelle vitesse les workers inutilisés disparaissent – trop court, cela génère des frais généraux de démarrage, trop long, cela mobilise de la RAM. Dans le cas du écouter-Socket, je choisis entre Unix-Socket et TCP. Un Unix-Socket réduit la charge et offre une attribution claire des droits via listen.owner, listen.group et listen.mode. Pour les deux variantes, je mets listen.backlog suffisamment élevé pour que les rafales entrantes aboutissent dans la mémoire tampon du noyau au lieu d'être immédiatement rejetées. Avec rlimit_files j'augmente si nécessaire le nombre de fichiers ouverts par travailleur, ce qui apporte de la stabilité en cas de nombreux téléchargements et chargements simultanés. Et si des priorités sont nécessaires, j'utilise priorité du processus, afin de traiter les pools peu critiques de manière quelque peu subordonnée du point de vue du CPU.

Slowlog et protection contre les blocages

Pour rendre visibles les requêtes lentes, j'active le Slowlog. Avec request_slowlog_timeout Je définis le seuil (par exemple 2 à 3 secondes) à partir duquel une trace de pile est enregistrée dans le slowlog est écrit. Je trouve ainsi des E/S bloquantes, des boucles coûteuses ou des verrous inattendus. Contre les véritables blocages, j'utilise request_terminate_timeout, qui interrompt brutalement lorsqu'une requête dure trop longtemps. Je considère que ces délais sont cohérents avec max_execution_time à partir du PHP et des délais d'expiration du serveur Web, afin qu'aucune couche ne se détache avant les autres. Dans la pratique, je commence de manière conservatrice, j'analyse les slowlogs sous charge et j'ajuste progressivement les seuils jusqu'à ce que les signaux soient significatifs, sans saturer le journal.

Opcache, memory_limit et leur influence sur la taille des workers

Je me réfère au Opcache dans ma planification RAM. Sa zone de mémoire partagée ne compte pas par travailleur, mais est utilisée conjointement par tous les processus. Taille et fragmentation (opcache.memory_consumption, interned_strings_buffer) influencent considérablement le temps de préchauffage et le taux de réussite. Un Opcache bien dimensionné réduit la pression sur le CPU et la RAM par requête, car moins de code est recompilé. Dans le même temps, je remarque que memory_limit: une valeur élevée protège certes contre les cas isolés de mémoire insuffisante, mais augmente le budget théorique dans le pire des cas par travailleur. Je planifie donc avec une moyenne mesurée plus une marge, et non avec la simple valeur memory_limit. Des fonctionnalités telles que le préchargement ou le JIT augmentent les besoins en mémoire – je les teste de manière ciblée et calcule la consommation supplémentaire dans le calcul pm.max_children.

Séparer et hiérarchiser les pools

Je partage les applications sur plusieurs pools lorsque les profils de charge diffèrent considérablement. Un pool pour le trafic frontal, un pour l'administration/le backend, un troisième pour Cron/les téléchargements : c'est ainsi que j'isole les pics et que j'attribue des limites différenciées. Pour les points d'extrémité rarement fréquentés, je définis ondemand avec un délai d'inactivité court pour le frontend dynamique avec une marge de manœuvre réduite. À propos de utilisateur/groupe et, le cas échéant,. chroot je veille à une isolation propre, tandis que les droits Socket régissent quel processus du serveur web peut accéder. Lorsque des priorités sont requises, le frontend reçoit plus pm.max_children et, le cas échéant, une priorité du processus, tandis que Cron/Reports fonctionne avec un budget plus modeste et une priorité moindre. Ainsi, l'interface utilisateur reste réactive, même lorsque des tâches lourdes sont exécutées en arrière-plan.

Utiliser correctement les points finaux d'état

Pour le diagnostic de durée de fonctionnement, j'active pm.status_path et en option ping.path par pool. Dans le statut, je vois les travailleurs actifs/inactifs qui File d'attente des listes, des compteurs liés au débit et des métriques de requêtes lentes. Une file d'attente de listes en croissance constante ou un nombre de travailleurs inactifs toujours égal à 0 sont pour moi des signaux d'alarme. Je protège ces points finaux derrière Auth et un réseau interne afin qu'aucun détail opérationnel ne soit divulgué à l'extérieur. De plus, j'active catch_workers_output, lorsque je souhaite collecter rapidement les données stdout/stderr des workers, par exemple en cas d'erreurs difficiles à reproduire. Je combine ces signaux avec des métriques système (RAM, CPU, E/S) afin de décider si je dois augmenter pm.max_children, ajuster les valeurs de réserve ou intervenir sur l'application.

Particularités dans les conteneurs et les VM

À l'adresse suivante : glanage et les petites VM, je tiens compte des limites cgroup et du risque lié au OOM killer. Je définis pm.max_children strictement selon le Limite de mémoire du conteneur et teste les pics de charge afin qu'aucun worker ne soit interrompu. Sans swap dans les conteneurs, la marge de sécurité est particulièrement importante. Pour les quotas CPU, j'adapte le nombre de workers au nombre de vCPU disponibles : si l'application est liée au CPU, un parallélisme accru entraîne plutôt des files d'attente qu'un débit. Les charges de travail liées à l'E/S supportent davantage de processus tant que le budget RAM le permet. De plus, je définis seuil_redémarrage_d'urgence et intervalle_de_redémarrage_d'urgence pour le processus maître, afin d'intercepter une spirale de plantage si un bug rare affecte plusieurs enfants en peu de temps. Ainsi, le service reste disponible pendant que j'analyse la cause.

Déploiements et rechargements fluides sans interruption de service

Je prévois Recharges de manière à ce que les requêtes en cours soient menées à bien. Un rechargement gracieux (par exemple via systemd reload) applique les nouvelles configurations sans interrompre brutalement les connexions ouvertes. Je maintiens le chemin d'accès au socket stable afin que le serveur web ne détecte aucune interruption de connexion. Lors des changements de version qui invalident une grande partie de l'Opcache, je préchauffe le cache (préchargement/requêtes de préchauffage) afin de limiter les pics de latence immédiatement après le déploiement. Je teste d'abord les modifications importantes sur un pool plus petit ou dans une instance Canary avec une configuration identique avant de déployer les valeurs à grande échelle. Chaque modification est enregistrée dans mon journal des modifications avec un horodatage et des captures d'écran des métriques, ce qui réduit le temps de dépannage en cas d'effets secondaires inattendus.

Comportement en rafale et files d'attente

Je compense les pics de charge avec un Conception des files d'attente Je mets listen.backlog suffisamment élevé pour que le noyau puisse temporairement mettre en mémoire tampon davantage de tentatives de connexion. Du côté du serveur web, je limite le nombre maximal de connexions FastCGI simultanées par pool de manière à ce qu'il soit pm.max_children convient. Ainsi, les rafales s'accumulent plutôt brièvement dans le serveur web (peu coûteux) que profondément dans PHP (coûteux). Je mesure la File d'attente des listes dans l'état FPM : s'il augmente régulièrement, j'augmente soit le nombre de travailleurs, j'optimise les taux de réussite du cache ou je réduis les valeurs Keep-Alive agressives. L'objectif est, en cas de pics, de Time-to-First-Byte stable, au lieu de laisser les requêtes s'enlisent dans des files d'attente interminables.

Flux de travail pratique pour les ajustements

Je commence par un Audit: budget RAM, taille du processus, profils E/S. Ensuite, je définis des valeurs de départ conservatrices pour pm.max_children et le mode pm. Je procède ensuite à des tests de charge ou observe les pics d'activité réels. J'enregistre toutes les modifications, y compris les métriques et les plages horaires. Après chaque ajustement, je vérifie la RAM, la latence P50/P95 et les taux d'erreur. Ce n'est qu'ensuite que je passe à l'étape suivante.

Lorsque je me retrouve à plusieurs reprises à la limite, je ne réagis pas immédiatement de manière excessive. Travailleur-Nombre. Je commence par optimiser les requêtes, les taux de réussite du cache et les fonctions coûteuses. Je déplace les tâches gourmandes en E/S vers des files d'attente et raccourcis les temps de réponse. Ce n'est que lorsque l'application fonctionne efficacement que j'augmente la taille du pool. Ce processus permet d'économiser des ressources et d'éviter des dommages consécutifs ailleurs.

Scénarios types : exemples de valeurs

Sur un serveur virtuel de 2 Go, je réserve environ 1 GB pour PHP-FPM et je définis une consommation de worker d'environ 50 à 60 Mo. Je commence donc avec pm.max_children à 15-20 et j'utilise dynamic avec une petite quantité de départ. Je maintiens min_spare à 2-3 et max_spare à 5-6. Je définis pm.max_requests à 500 afin que les processus soient régulièrement remplacés. Ces paramètres garantissent des temps de réponse stables pour les petits projets.

Avec 8 Go de RAM, je prévois généralement 4 à 6 Go pour PHP et je définis des tailles de worker comprises entre 60 et 80 Mo. Il en résulte 30 à 80 processus enfants comme plage de démarrage. pm.start_servers est compris entre 15 et 20, min_spare entre 10 et 15, max_spare entre 25 et 30. Je choisis pm.max_requests entre 500 et 800. Sous charge, je vérifie si le pic de RAM laisse de la marge, puis j'augmente prudemment.

Dans les configurations à forte charge avec plus de 16 Go de RAM, je réserve 10 à 12 Go pour FPM. Avec 70 à 90 Mo par travailleur, j'arrive rapidement à 100 à 160 processus. Le choix entre statique et dynamique dépend de la forme de la charge. Le mode statique est convaincant pour une charge élevée permanente, le mode dynamique pour une demande fluctuante. Dans les deux cas, une surveillance constante reste obligatoire.

Éviter les obstacles et fixer des priorités

Je ne confonds pas le nombre de Visiteurs avec le nombre de scripts PHP simultanés. De nombreuses consultations de pages atteignent les caches, fournissent des fichiers statiques ou bloquent en dehors de PHP. C'est pourquoi je dimensionne pm.max_children en fonction du temps PHP mesuré, et non en fonction des sessions. Si les processus sont définis de manière trop économe, je constate des requêtes en attente et des taux d'erreur croissants. Si les valeurs sont trop élevées, la mémoire bascule dans le swap et tout ralentit.

Une erreur fréquente : plus de processus signifie plus de Vitesse. En réalité, c'est l'équilibre entre le CPU, l'E/S et la RAM qui compte. Si le CPU atteint 100 % et que la latence augmente rapidement, l'ajout de travailleurs supplémentaires n'aide guère. Il vaut mieux éliminer le véritable goulot d'étranglement ou réduire la charge via le cache. Le guide explique pourquoi les travailleurs sont souvent le goulot d'étranglement. PHP Worker comme goulot d'étranglement.

En bref

Je détermine d'abord le réel RAM-Consommation par travailleur et je définis pm.max_children avec une marge. Ensuite, je sélectionne le mode pm adapté à la forme de charge et j'équilibre les valeurs de démarrage et de réserve. Avec pm.max_requests, je maintiens les processus à jour sans surcharge inutile. Je transfère les journaux, les statuts et les métriques vers un système de surveillance propre afin que chaque modification reste mesurable. Je parviens ainsi à obtenir des temps de réponse courts, des pools stables et une charge serveur qui dispose de réserves pour les pics.

Derniers articles