Fragmentation de la mémoire Dans l'hébergement Web, PHP-FPM et MySQL ralentissent, même si la mémoire RAM semble suffisante, car la mémoire est fragmentée en nombreux petits blocs et les allocations plus importantes échouent. Je montre de manière pratique comment la fragmentation augmente le coût des requêtes, déclenche le swap et pourquoi un réglage ciblé de PHP et MySQL améliore visiblement les temps de chargement, la fiabilité et la scalabilité.
Points centraux
- PHP-FPM Recycler : redémarrer régulièrement les processus via pm.max_requests
- Tampon dosage : maintenir le tampon MySQL par connexion à un niveau prudent
- Swap À éviter : réduire la swappiness, tenir compte du NUMA
- tableaux Entretenir : vérifier Data_free, optimiser de manière ciblée
- Suivi : anticiper les tendances et agir rapidement
Que signifie la fragmentation de la mémoire dans le quotidien de l'hébergement ?
Dans l'hébergement, rencontre Fragmentation sur les processus de longue durée qui demandent et libèrent constamment de la mémoire, ce qui crée des lacunes dans l'espace d'adressage. Bien que la somme de la RAM libre semble importante, il manque des blocs contigus pour les allocations plus importantes, ce qui ralentit les tentatives d'allocation. Je constate ce phénomène dans les workers PHP‑FPM et dans mysqld, qui semblent de plus en plus „ gonflés “ au fil des heures. Cet effet rend chaque requête légèrement plus coûteuse et augmente sensiblement les temps de réponse sous charge. Cela ralentit considérablement les pics d'activité tels que les promotions commerciales ou les sauvegardes, même si le CPU et le réseau restent discrets.
Pourquoi PHP-FPM génère-t-il de la fragmentation ?
Chaque worker PHP‑FPM charge le code, les plugins et les données dans son propre Espace d'adressage, traite diverses requêtes et laisse des espaces dispersés lors de la libération. Au fil du temps, les processus se développent et libèrent de la mémoire en interne, mais pas nécessairement au niveau du système d'exploitation, ce qui augmente la fragmentation. Différents scripts, tâches d'importation et traitements d'images renforcent ce mélange et entraînent des modèles d'allocation variables. J'observe cela comme une augmentation insidieuse de la RAM, même si la charge et le trafic semblent constants. Sans recyclage, cette fragmentation interne ralentit l'allocation et rend difficile la planification en cas de forte fréquentation.
Conséquences notables sur les temps de chargement et la fiabilité
Les processus fragmentés génèrent davantage Overhead dans la gestion de la mémoire, ce qui se traduit par des backends d'administration plus lents et des paiements plus hésitants. Les boutiques WordPress ou les grandes instances CMS, en particulier, réagissent lentement lorsque de nombreuses requêtes simultanées rencontrent des workers fragmentés. Il en résulte des délais d'attente, des erreurs 502/504 et des tentatives répétées du côté NGINX ou Apache. Je lis ces situations dans des métriques telles que les pics de temps de réponse, l'augmentation de la ligne de base de la RAM et l'augmentation soudaine de l'utilisation du swap. Ignorer cela revient à sacrifier les performances, à détériorer l'expérience utilisateur et à augmenter le taux d'abandon dans les entonnoirs critiques.
Configurer correctement PHP-FPM : limites, pools, recyclage
Je mise sur des Limites, des pools séparés et un recyclage systématique pour limiter la fragmentation. Je termine pm.max_requests de manière à ce que les workers redémarrent régulièrement sans perturber les visiteurs actuels. Pour les profils de trafic avec des pics de charge, pm = dynamic est souvent plus adapté, tandis que pm = ondemand permet d'économiser de la RAM sur les sites peu fréquentés. Je maintiens délibérément la limite de mémoire par site à un niveau modéré et l'ajuste en fonction des scripts réels ; le sujet fournit une introduction à ce sujet. Limite de mémoire PHP. De plus, je sépare les projets très sollicités dans des pools distincts afin qu'un élément gourmand en mémoire n'affecte pas tous les sites.
OPcache, préchargement et allocateur PHP en bref
Afin de limiter la fragmentation dans le processus PHP, je mise sur un dimensionnement propre. OPcache. Une valeur opcache.memory_consumption généreuse mais pas excessive et suffisamment de chaînes internes réduisent les allocations répétées par requête. Je surveille le taux de réussite, le gaspillage et la capacité restante ; si le gaspillage augmente avec le temps, il vaut mieux planifier un rechargement plutôt que de laisser les travailleurs se multiplier de manière incontrôlée. Le préchargement peut maintenir le code chaud stable en mémoire et ainsi lisser les modèles d'allocation, à condition que la base de code soit préparée en conséquence. De plus, je fais attention à la Choix de l'allocateur: selon la distribution, PHP‑FPM et les extensions fonctionnent avec différentes implémentations Malloc. Dans certaines configurations, des allocateurs alternatifs tels que jemalloc réduisent sensiblement la fragmentation. Cependant, je ne déploie ces modifications qu'après les avoir testées, car le débogage, le profilage DTrace/eBPF et les vidages de mémoire réagissent différemment selon l'allocateur.
Pour les tâches gourmandes en mémoire telles que le traitement d'images ou les exportations, je préfère utiliser des pools séparés avec des limites plus strictes. Ainsi, le pool principal ne grossit pas de manière incontrôlée et la fragmentation reste isolée. De plus, je limite les extensions gourmandes en mémoire (par exemple via des variables d'environnement) et j'utilise la contre-pression : les requêtes qui nécessitent de grands tampons sont ralenties ou transférées vers des files d'attente asynchrones, au lieu de surcharger tous les travailleurs en même temps.
Comprendre la mémoire MySQL : tampons, connexions, tables
Dans MySQL, je fais la distinction entre les variables globales Tampon comme le pool de tampons InnoDB, les tampons par connexion et les structures temporaires, qui peuvent augmenter à chaque opération. Des valeurs trop élevées entraînent une explosion des besoins en RAM et une fragmentation accrue au niveau du système d'exploitation en cas de charge de connexion élevée. De plus, les tables se fragmentent à cause des mises à jour/suppressions et laissent des parties Data_free qui nuisent à l'utilisation du pool de tampons. Je vérifie donc régulièrement la taille, les taux de réussite et le nombre de tables temporaires sur disque. L'aperçu suivant m'aide à identifier avec précision les symptômes typiques et à évaluer les mesures à prendre.
| Symptôme | Cause probable | Mesure |
|---|---|---|
| La RAM augmente régulièrement, le swap commence | Tampon trop grand ou trop nombreux tampons par connexion | Limiter la taille de la file d'attente, réduire via le tampon de connexion |
| Beaucoup de sorts/joints lents | Index manquants, tampons sort/join excessifs | Vérifier les index, conserver sort/join conservateur |
| Grande quantité de données libres dans les tables | Mises à jour/suppressions importantes, pages fragmentées | OPTIMISEZ de manière ciblée, archivez, rationalisez les schémas |
| Pics dans les tables de disques temporaires | tmp_table_size trop petit ou requêtes inappropriées | Augmenter modérément les valeurs, restructurer les requêtes |
Optimisation de la mémoire MySQL : choisir les tailles plutôt que les dépasser
Je choisis le pool de tampons InnoDB de manière à ce que le Système d'exploitation conserve suffisamment de mémoire pour le cache du système de fichiers et les services, en particulier sur les serveurs combinés avec Web et DB. Je redimensionne de manière conservatrice les tampons par connexion tels que sort_buffer_size, join_buffer_size et read_buffer afin que de nombreuses connexions simultanées n'entraînent pas de surcharge de la mémoire vive. Je définis tmp_table_size et max_heap_table_size de manière à ce que les opérations sans importance ne nécessitent pas d'énormes tables en mémoire. Pour d'autres paramètres, je trouve sous Performances MySQL Des pistes de réflexion utiles. Le point décisif reste le suivant : je préfère régler un peu plus juste et mesurer, plutôt que d'augmenter à l'aveuglette et risquer la fragmentation et le swap.
InnoDB en détail : stratégies de reconstruction et instances de pool
Afin de maintenir MySQL „ compact “ en interne, je prévois de procéder régulièrement à des Reconstructions pour les tables comportant une forte proportion d'écritures. Une commande OPTIMIZE TABLE ciblée (ou une reconstruction en ligne via ALTER) regroupe les données et les index et réduit Data_free. Je choisis des plages horaires où la charge est faible, car les reconstructions sont gourmandes en E/S. L'option innodb_file_per_table Je le garde actif, car il permet des reconstructions contrôlées par table et réduit le risque que certains „ éléments problématiques “ fragmentent l'ensemble du fichier d'espace de table.
J'utilise plusieurs Instances de pool tampon (innodb_buffer_pool_instances) par rapport à la taille du pool et aux cœurs CPU afin de soulager les verrous internes et de répartir les accès. Cela améliore non seulement le parallélisme, mais lisse également les modèles d'allocation dans le pool. Je vérifie également la taille des journaux de reprise et l'activité des threads de purge, car l'historique accumulé peut occuper de la mémoire et des E/S, ce qui augmente la fragmentation au niveau du système d'exploitation. Il est important de modifier les paramètres progressivement, de les mesurer et de ne les conserver que si les latences et les taux d'erreur diminuent réellement.
Éviter les swaps : paramètres du noyau et NUMA
Dès que Linux active le swap, les temps de réponse augmentent ordres de grandeur, car les accès à la RAM deviennent trop lents en E/S. Je réduis considérablement vm.swappiness afin que le noyau utilise plus longtemps la RAM physique. Sur les hôtes multi-CPU, je vérifie la topologie NUMA et active si nécessaire l'entrelacement afin de réduire l'utilisation inégale de la mémoire. Pour les informations de fond et l'influence du matériel, la perspective m'aide à Architecture NUMA. De plus, je prévois des réserves de sécurité pour le cache de page, car un cache affamé accélère la fragmentation de l'ensemble de la machine.
Pages transparentes volumineuses, surengagement et choix de l'allocateur
Pages transparentes volumineuses (THP) peuvent entraîner des pics de latence dans les bases de données, car la fusion/division de grandes pages se produit à des moments inopportuns. Je règle THP sur „ madvise “ ou le désactive lorsque MySQL réagit trop lentement sous la charge. Dans le même temps, je veille à ce que Overcommit: Une configuration vm.overcommit_memory trop généreuse expose au risque d'OOM kills, notamment lorsque la fragmentation rend les blocs cohérents volumineux rares. Je privilégie les paramètres d'overcommit conservateurs et vérifie régulièrement les journaux du noyau à la recherche de signes de pression mémoire.
De même, les Choix de l'allocateur Au niveau du système, cela vaut la peine d'y jeter un œil. glibc‑malloc, jemalloc ou tcmalloc se comportent différemment en termes de fragmentation. Je teste toujours les alternatives de manière isolée, je mesure l'historique RSS et les latences, et je ne déploie les modifications que si les métriques restent stables sous le trafic réel. Les avantages varient considérablement en fonction de la charge de travail, de la combinaison d'extensions et de la version du système d'exploitation.
Identifier la fragmentation : indicateurs et conseils
Je fais attention à augmenter lentement lignes de base pour la RAM, davantage de réponses 5xx sous charge et des retards dans les actions administratives. Un coup d'œil aux statistiques PM de PHP-FPM permet de voir si les enfants atteignent leurs limites ou vivent trop longtemps. Dans MySQL, je vérifie les taux de réussite, les tables temporaires sur le disque et Data_free par table. En parallèle, les métriques du système d'exploitation telles que les erreurs de page, les indicateurs swap-in/out et de fragmentation de la mémoire aident en fonction de la version du noyau. En combinant ces signaux, on peut identifier rapidement les schémas et prendre des mesures planifiées.
Approfondir le suivi : comment je rassemble les signaux
Je corrèle Métriques d'application (latences p95/p99, taux d'erreur) avec métriques de processus (RSS par FPM Worker, mémoire mysqld) et Valeurs OS (Pagecache, Slab, Major Faults). Dans PHP‑FPM, j'utilise l'interface d'état pour les longueurs de file d'attente, les enfants actifs/spawned et la durée de vie des travailleurs. Dans MySQL, j'observe Created_tmp_disk_tables, Handler_write/Handler_tmp_write, ainsi que les Buffer‑Pool‑Misses. En complément, je consulte les cartes de processus (pmap/smaps) afin de déterminer si de nombreuses petites arènes ont été créées. Ce qui est important pour moi, c'est la orientation des tendances: ce n'est pas le pic individuel, mais le déplacement progressif sur plusieurs heures/jours qui détermine si la fragmentation devient un réel danger.
Routine pratique : maintenance et gestion des données
Je range régulièrement Données sur : les sessions expirées, les anciens journaux, les révisions inutiles et les caches orphelins. Pour les tables très variables, je planifie des fenêtres OPTIMIZE ciblées afin de fusionner les pages fragmentées. Je répartis dans le temps les tâches d'importation volumineuses ou les vagues Cron afin que tous les processus ne sollicitent pas simultanément le tampon maximal. Dans le cas de projets en pleine croissance, je sépare très tôt le Web et la base de données afin d'isoler les modèles gourmands en mémoire. Cette discipline permet de maintenir la cohérence de la mémoire vive et de réduire le risque de latences de pointe.
Calculer correctement les tailles : dimensionner les limites et les pools
Je détermine pm.max_children en fonction de la mémoire RAM réellement disponible pour PHP. Pour cela, je mesure le RSS moyen d'un worker sous charge réelle (extensions et OPcache inclus) et j'ajoute des marges de sécurité pour les pics. Exemple : sur un hôte de 16 Go, je réserve 4 à 6 Go pour le système d'exploitation, le cache de page et MySQL. Il reste 10 Go pour PHP ; avec 150 Mo par worker, cela donne théoriquement 66 enfants. Dans la pratique, je règle pm.max_children sur ~80-90% de cette valeur afin de laisser une marge pour les pics, soit environ 52-58. pm.max_requests Je choisis de recycler les workers avant qu'ils ne soient trop fragmentés (souvent entre 500 et 2 000, selon la combinaison de codes).
Pour MySQL, je calcule le Pool de mémoire tampon à partir de la taille des données actives, et non de la taille totale de la base de données. Les tables et index importants doivent pouvoir y tenir, mais le cache du système d'exploitation a besoin d'espace pour les journaux binaires, les sockets et les ressources statiques. Pour le tampon par connexion, je calcule avec le parallélisme maximal réaliste. Si 200 connexions sont possibles, je ne dimensionne pas de manière à ce que plusieurs gigaoctets par connexion explosent au total, mais je fixe des limites strictes qui ne présentent pas de risque de swap, même en période de pointe.
Dissocier les files d'attente, le traitement d'images et les tâches secondaires
De nombreux problèmes de fragmentation surviennent lorsque emplois à temps partiel les mêmes pools que les requêtes frontales. Pour les exportations, les crawls, les conversions d'images ou les mises à jour d'index de recherche, j'utilise des pools FPM séparés ou des tâches CLI avec des ulimitJe limite également les opérations sur les images avec GD/Imagick en fixant des limites de ressources appropriées, afin que les conversions individuelles volumineuses ne „ saturent “ pas tout l'espace d'adressage. Je planifie les tâches de manière décalée dans le temps et leur attribue des limites de concurrence spécifiques afin qu'elles ne saturent pas le chemin d'accès frontal.
Conteneurs et virtualisation : cgroups, OOM et effets ballon
Dans les conteneurs, j'observe Limites de mémoire et le OOM Killer en particulier. La fragmentation fait que les processus atteignent plus rapidement les limites du cgroup, même si l'hôte dispose encore de RAM libre. J'aligne strictement pm.max_children sur la limite du conteneur et je garde suffisamment de réserve pour amortir les pics. J'évite le swapping à l'intérieur des conteneurs (ou sur l'hôte), car l'indirection supplémentaire amplifie les pics de latence. Dans les VM, je vérifie le balloning et le KSM/UKSM ; une déduplication agressive permet certes d'économiser de la RAM, mais peut entraîner une latence supplémentaire et fausser l'image de la fragmentation.
Brève liste de contrôle sans puces
Je commence par fixer un objectif réaliste. memory_limit par site et observe le comportement de l'utilisation maximale au fil des jours. Ensuite, je règle PHP-FPM avec des valeurs pm appropriées et un pm.max_requests raisonnable afin que les workers fragmentés fonctionnent comme prévu. Pour MySQL, je me concentre sur une taille de pool de tampons appropriée et des tampons par connexion conservateurs plutôt que sur des augmentations forfaitaires. Côté noyau, je réduis la swappiness, vérifie les paramètres NUMA et garde des réserves pour le cache de page. Enfin, j'évalue les tables présentant des anomalies Data_free et planifie des optimisations en dehors des activités quotidiennes.
En bref : ce qui compte dans l'entreprise
Je parviens à lutter efficacement contre la fragmentation de la mémoire en appliquant systématiquement recyclage le PHP‑FPM‑Worker, des limites raisonnables et des pools propres. MySQL bénéficie de tailles raisonnables pour le buffer pool et le buffer par connexion, ainsi que de tables bien rangées. J'évite de manière proactive le swap en tenant compte du swappiness et du NUMA et en réservant de la RAM libre pour le cache de fichiers. La surveillance permet de détecter les tendances insidieuses avant que les utilisateurs ne s'en aperçoivent et permet des interventions planifiées en toute tranquillité. En utilisant ces leviers de manière disciplinée, vous pouvez maintenir PHP et MySQL plus rapides, plus fiables et plus rentables sans avoir à procéder à des mises à niveau matérielles immédiates.


