...

Fragmentation de la mémoire dans le fonctionnement des serveurs : causes et solutions

La fragmentation de la mémoire dans l'exploitation des serveurs a pour conséquence que, malgré la RAM libre, il n'y a plus de grands blocs contigus disponibles et que les allocations critiques échouent. Je présente les causes, les symptômes typiques et les contre-mesures ciblées pour que Serveur réagir de manière calculable et rétablir des allocations fiables fonctionnent.

Points centraux

  • Interne et externe Distinguer la fragmentation et l'adresser de manière ciblée.
  • Allocateur de Buddy comprendre : Ordres, fractionnements, fusions manquantes.
  • Ski de fond-Régler correctement les charges de travail, l'overhead de l'hyperviseur et le THP.
  • Diagnostic avec buddyinfo, vmstat et les métriques de compaction.
  • Modèles d'allocation améliorer la situation : Mettre en commun, pré-allouer, séparer les durées de vie.

Que signifie la fragmentation de la mémoire dans le quotidien des serveurs ?

Je désigne par Mémoire fragmentation l'état dans lequel la mémoire libre se décompose en de nombreux petits espaces et où les grandes requêtes n'obtiennent plus de zone contiguë. La fragmentation interne se produit lorsqu'un bloc alloué est plus grand que la demande réelle et que des octets inutilisés restent dans le bloc, ce qui Efficacité de la mémoire. La fragmentation externe se produit lorsque les sections libres sont dispersées et ne peuvent plus se rassembler en une grande zone, bien qu'il y ait suffisamment de RAM disponible au total. C'est précisément là que les grands tampons, les réservations JIT ou les pilotes qui privilégient la mémoire contiguë échouent en raison de la pénurie apparemment paradoxale de grands blocs. Dans les environnements d'hébergement, les charges parallèles élevées, la longue durée de fonctionnement et les piles de logiciels hétérogènes aggravent ce problème. Dynamique perceptible.

Comment l'allocateur Linux Buddy génère de la fragmentation

Le noyau Linux gère la mémoire physique via un Buddy-Allocateur qui organise les pages en classes de taille (ordres), en commençant par 4 Ko. Lorsque les processus demandent des zones plus grandes, le noyau divise les grands blocs en "buddies" jusqu'à ce qu'une taille appropriée soit disponible ; lors de la libération, il essaie de réunir les "buddies". Cependant, des demandes de longueurs différentes, des durées de vie variables et des libérations inégales empêchent la recombinaison et favorisent les blocs externes. Fragmentation. Avec le temps, le stock de gros ordres se vide tandis que les petits ordres gonflent - /proc/buddyinfo affiche alors des chiffres élevés dans les ordres bas et des zéros dans les ordres élevés. A partir de ce point, la compaction et éventuellement le comportement OOM interviennent plus fréquemment, ce qui génère des latences et renforce les perturbations.

Causes dans les environnements d'hébergement et de virtualisation

Les charges de travail à long terme sur le Web et les bases de données créent un modèle d'allocation variable qui fragmente les grands blocs et permet d'éviter les interruptions ultérieures. Fusionner évite les problèmes. Les frameworks et les bibliothèques qui libèrent de la mémoire tardivement ou de manière non coordonnée laissent des espaces vides dans lesquels seules les petites requêtes trouvent leur place. La virtualisation ajoute ses propres frais généraux et déplace les allocations dans l'invité et l'hyperviseur, ce qui entraîne des coûts externes. Fragmentation se produit plus rapidement. Des valeurs vm.min_free_kbytes mal définies augmentent la pression parce que le noyau ne garde pas assez de tampons pour les allocations atomiques ou les sur-réserve. Plus de transparence sur mémoire virtuelle m'aide à ordonner proprement l'interaction entre l'allocateur d'invités, THP, Huge Pages et l'hyperviseur.

Impact sur les performances et l'expérience utilisateur

Si l'accumulateur se décompose en de nombreux petits îlots, les Latence, Le noyau compresse et déplace plus souvent avant de pouvoir répondre aux grandes demandes. Les applications qui ont besoin de zones continues - comme les bases de données, les caches ou les pipelines multimédias - sont plus rapidement mises en difficulté. Malgré une RAM „libre“, les grandes allocations échouent et génèrent des messages d'erreur, des redémarrages ou des arrêts brutaux, ce qui entraîne des sessions et des Transactions est affectée. Les activités en arrière-plan telles que la compression augmentent la charge du processeur et la pression des E/S, ce qui ralentit également les charges de travail par ailleurs légères. Dans les scénarios d'hébergement, cela se traduit par des temps de réponse longs, des délais d'attente sporadiques et une moins bonne mise à l'échelle lors des pics de charge.

Diagnostic : de buddyinfo aux métriques de compaction

Je vérifie d'abord /proc/buddyinfo pour voir quels sont les Ordres vmstat et sar indiquent le nombre de compactages du noyau ou si le chemin OOM a été activé, ce qui indique une pression due à de grandes allocations. Avec perf et strace, je peux voir si les threads attendent une compaction directe et si les temps de réponse fluctuent, ce qui se manifeste de manière sensible dans les logs et les métriques. Dans les environnements de serveurs Windows, je visualise les tas fragmentés à l'aide d'outils de débogage afin de vérifier l'absence de lacunes et d'ajuster finement les paramètres du tas. ajuster. En outre, je mesure le plus grand bloc libre, car la somme des RAM libres ne suffit pas comme diagnostic.

Le réglage du noyau et des VM en pratique

Je fixe vm.min_free_kbytes à un niveau modérément plus élevé, souvent dans le couloir de 5-10 % de la RAM, afin que le noyau puisse utiliser de grandes quantités de mémoire atomique. Demandes de manière fiable. J'active les Transparent Huge Pages avec précaution : soit à la demande, soit par madvise, en fonction du profil de charge et du risque de fragmentation. Les pages géantes statiques offrent de la prévisibilité, mais nécessitent une planification propre afin de ne pas être bloquées à d'autres endroits. Goulots d'étranglement de créer des problèmes. La compaction déclenche l'ordre à court terme, mais ne remplace pas une solution structurelle pour des modèles durables et instables. J'intègre les topologies NUMA dans le réglage afin que les grandes allocations restent locales et ne s'effilochent pas en travers des nœuds.

Réglage Objectif Avantages Remarque
vm.min_free_kbytes Réserve pour les grandes allocations Moins de pics d'OOM/compaction Augmenter progressivement la valeur et la mesurer
THP (on/madvise) Préférer les pages plus grandes Moins de fragmentation, meilleur taux de TLB Attention aux temps de latence de la charge de travail
Pages géantes (statique) Réserver des zones continues Grands blocs prévisibles Planifier la capacité à l'avance
Compaction Réduire les zones libres Blocs temporairement plus grands Augmente le CPU/I&O à court terme
NUMA-politique Assurer l'allocation locale Latence plus faible, moins de cross-traffic Configurer l'équilibrage

Zones de stockage, types de migrations et pourquoi „unmovable“ bloque tout

Le Page-Allocator ne travaille pas seulement avec des ordres, mais aussi avec des zones (DMA, DMA32, Normal, Movable) et Types de migrants (MOVABLE, UNMOVABLE, RECLAIMABLE). Les granulés pour cela sont les „pageblocks“. Dès que des pages non amovibles (par exemple des structures de noyau, des pages épinglées par des pilotes) se retrouvent dans un pageblock, le noyau marque ce bloc comme difficilement déplaçable. Ce sont précisément ces blocs „contaminés“ qui empêchent les zones libres de compaction de devenir de grands blocs contigus. Domaines forme. Je planifie donc délibérément la capacité dans ZONE_MOVABLE (lorsque c'est possible) et je veille à ce que les données d'application soient principalement allouées en tant que MOVABLE. De cette manière, les réserves importantes et cohérentes restent plus facilement disponibles. Pour les charges de travail nécessitant une DMA élevée, j'utilise des réservations ciblées afin que les pages UNMOVABLE n'écrasent pas la large zone normale.

Concevoir proprement les modèles d'allocation

Je regroupe les demandes de stockage par Durée de vieLes objets à courte durée de vie sont placés dans des pools, les objets à longue durée de vie dans des régions séparées, afin d'éviter que les libérations n'éventrent tout. Je place les tailles fréquentes dans des pools fixes afin de réduire la fluctuation des ordres et de décharger le Buddy Allocator. Je planifie les grands buffers au départ au lieu de les demander au milieu du trafic, ce qui me permet d'éviter les pics de charge lors de la contraction. J'adapte mes souhaits d'alignement aux besoins réels, car les alignements excessifs gaspillent de l'espace et favorisent les erreurs internes. Fragmentation. Dans les pipelines de construction et de déploiement, je teste les chemins de stockage avec des scénarios de charge avant que le trafic n'arrive en direct.

Choix de l'allocateur dans l'espace utilisateur : glibc, jemalloc, tcmalloc

Toute fragmentation n'est pas un problème de noyau. Le site Espace utilisateur-allocateur a une grande influence sur le modèle que l'allocateur buddy voit à la fin. glibc malloc utilise des arènes per-thread ; sur de nombreux cœurs, cela peut conduire à une fragmentation interne élevée. Je limite le nombre d'arènes et j'élague de manière plus agressive pour que les zones inutilisées reviennent plus rapidement au système d'exploitation. Des alternatives comme jemalloc ou tcmalloc offrent des classes de taille plus fines et des modèles de partage plus cohérents, ce qui peut réduire sensiblement la fragmentation externe. Ce qui est décisif : Je mesure sous la charge de production, car chaque allocateur a d'autres trade-offs en termes de latence, de débit et d'empreinte mémoire. Pour les services à haut débit et les tailles d'objets uniformes, les arènes dédiées ou les pools de type slab fournissent souvent les données les plus stables. Latence.

Mesures côté application : Java, PHP, Caches & bases de données

En Java, je mise sur Arènes ou Allocateur de régions, et je choisis des profils GC qui favorisent les grandes réservations contiguës au lieu de découper constamment le tas en fins morceaux. J'équilibre Xms/Xmx de manière à ce que le tas ne croisse et ne rétrécisse pas en permanence, car ce pompage favorise les trous. Pour les piles PHP et MySQL, j'utilise des pools de mémoire fixes, je limite les objets surdimensionnés et j'optimise la taille des tampons afin d'obtenir des modèles d'allocation cohérents. Optimisation PHP/MySQL. Je règle les systèmes de mise en cache (par exemple les caches d'objets ou de pages) sur des tailles de chunk uniformes, afin que les partages ne laissent pas constamment de gros trous. Si rien n'y fait, je planifie des redémarrages contrôlés dans des fenêtres de maintenance, plutôt que de risquer des événements OOM non planifiés qui pourraient perturber tout le système. Services de neutralisation.

Pratique des conteneurs et de Kubernetes

Les conteneurs ne modifient pas le fonctionnement du Buddy-Allocateurs - ils ne font que segmenter la vue et les limites. La fragmentation reste donc une question d'hôte, mais se manifeste dans les pods par des évictions, des latences fluctuantes ou des coûts de fractionnement THP. J'obtiens la stabilité en

  • Définir les classes de QoS (Guaranteed/Burstable) de manière à ce que les pods critiques reçoivent des réserves fixes et ne croissent et ne décroissent pas en même temps.
  • de mémoire réalistes, afin que le trimming et le reclaiming ne se heurtent pas en permanence à des limites dures. Frontières rebondir.
  • Configurer THP/Hugepages de manière cohérente pour l'ensemble de l'hôte et fournir aux pods qui ont besoin de grandes pages des pools réservés de manière statique.
  • utiliser des stratégies d'échauffement (pre-faulting, pré-allocation) pour que les grands blocs soient occupés tôt et ne soient pas demandés plus tard sous la charge.

Je surveille les nœuds conteneurisés comme Bare-Metal : buddyinfo, Compaction-Events, OOM-Kills - seulement je corrige en plus avec Pod-Restarts et Evictions pour séparer proprement la cause.

Virtualisation, NUMA et influences matérielles

Parmi les hyperviseurs, j'examine la manière dont l'allocateur invité, le ballooning et le THP hôte interagissent, car la stratification peut renforcer la fragmentation et entraîner des problèmes de taille. Blocs de manière serrée. Je respecte systématiquement les topologies NUMA : l'allocation locale réduit la latence et empêche que les grandes requêtes soient réparties sur les nœuds et donc coupées plus petites. J'épingle les charges de travail aux nœuds NUMA lorsque cela est pertinent et j'observe l'effet sur les erreurs de page et les succès TLB. Pour un contrôle plus fin, je définis des directives pour les nœuds de stockage et j'applique des règles de gestion de la mémoire. NUMA-Balancing de manière ciblée. J'intègre également les mises à jour du microcode et du micrologiciel afin d'exclure les effets secondaires inattendus et d'assurer la prédictibilité des grands projets. Exigences de l'argent.

Pilotes de périphériques, DMA et CMA

Les pilotes qui sont physiquement contiguës (par ex. certains moteurs DMA, le multimédia, les cartes de capture), aggravent la fragmentation externe. Dans ce cas, je prévois d'utiliser l'allocateur de mémoire contiguë (CMA) ou de réserver de grands blocs tôt dans le processus de démarrage. J'évite ainsi que de nombreuses petites allocations „grignotent“ l'espace d'adressage avant que le pilote ne reçoive ses tampons. En même temps, j'isole les pages épinglées (par exemple par RDMA/DPDK) de la mémoire générale de l'application, afin que leur caractère UNMOVABLE ne rende pas inutilisables des blocs de pages entiers. Je devrais également vérifier si les configurations IOMMU virtualisent suffisamment les grandes zones non contiguës - dans le cas contraire, j'ai besoin de réserves ciblées et d'un calendrier clair. Fenêtre pour ces allocations.

Routine opérationnelle : utiliser intelligemment le monitoring et les fenêtres de maintenance

J'ancre des snapshots buddyinfo, des compteurs de compaction et des événements OOM dans mon Suivi, pour voir les tendances plutôt que les événements isolés. Je réduis les déploiements en continu de manière à ce que les fluctuations de mémoire soient concentrées dans des fenêtres de temps et que le reste de la semaine soit plus calme. Pendant les fenêtres de maintenance, je déclenche manuellement la compression si nécessaire, je nettoie les caches et je redémarre les services avant que la fragmentation ne provoque des douleurs productives. Je corrèle les logs et les métriques avec le trafic de pointe afin de reconnaître les modèles récurrents et d'adapter les tampons de manière ciblée. En cas de changements importants, je teste d'abord en staging pour éviter les surprises. Effets de bord en direct.

Le runbook : Quand les grandes allocations échouent aujourd'hui

Si des messages d'erreur „order X allocation failed“ apparaissent de manière aiguë, je travaille par étapes claires :

  1. État des lieux : Sauvegarder buddyinfo, vérifier vmstat (allocstall/compact), rechercher les entrées compaction/OOM dans dmesg. Estimer le plus grand bloc libre (ordre le plus élevé avec >0).
  2. Soulagement à court terme : Mettre en pause les services non critiques, réduire la charge, vider les caches de manière ciblée. Lancer manuellement la compaction et désamorcer temporairement le défragment THP s'il est nuisible.
  3. Dégagement ciblé : Faire reconstruire de grandes mémoires tampon contiguës dans des services définis (redémarrage contrôlé) avant le prochain pic.
  4. Augmenter la réserve : vm.min_free_kbytes et soulever prudemment les filigranes pour sécuriser les allocations atomiques pour les prochaines heures ; effets étroits moniteurs.
  5. Remède permanent : Corriger les modèles d'allocation, introduire des pools, déplacer la pré-allocation vers le départ, vérifier la localisation du NUMA et régler proprement le THP/Huge Pages.

Grandeurs de mesure, SLO et alarme

Je ne me contente pas de mesurer les sommes de RAM, je définis des SLOs pour la capacité d'allocation : „ordre le plus élevé avec disponibilité“, „temps jusqu'à la grande allocation réussie“, „part de compaction stall“. J'en déduis des alertes qui se déclenchent tôt, avant que les utilisateurs ne voient les délais d'attente. Les indicateurs utiles sont entre autres

  • Nombre de blocs libres dans les ordres élevés (par ex. ≥ ordre-9) par minute.
  • Fréquence et durée des attentes directes de compaction ou de reclaim.
  • Proportion de pages épinglées/inamovibles par rapport à la mémoire totale.
  • Taux de réussite des grandes allocations lors des tests de charge et après les déploiements.

Je relie ces métriques aux dates de sortie, aux pics de trafic et aux changements de configuration. J'identifie ainsi des modèles qui me permettent d'anticiper les réserves. mettre à l'échelle ou de la fenêtre de répartition.

Planification des capacités et conscience des coûts

Je calcule les marges de stockage de telle sorte que tant Fonctionnement normal que les phases de maintenance avec des allocations plus élevées sont couvertes proprement. Au lieu d'augmenter globalement la capacité, j'examine d'abord les corrections de modèles, car un bon réglage est souvent plus efficace que de la RAM supplémentaire. Si j'augmente la capacité, je prévois des réserves pour THP/Huge Pages, afin que les grandes pages n'entrent pas en collision avec les pics d'application. La consolidation sur des hôtes moins nombreux mais plus puissants en termes de mémoire peut réduire la fragmentation, à condition que je définisse le NUMA et les profils d'allocation de manière appropriée. En fin de compte, j'économise des coûts en euros en réduisant la fragmentation, car je diminue les pics d'UC et la congestion d'E/S et j'obtiens des licences plus efficaces. utilise.

En bref

La fragmentation de la mémoire se produit lorsque de nombreuses attributions de longueurs et de tailles différentes contiennent des données liées. Domaines et que les demandes importantes ne soient pas satisfaites. Je résous le problème sur trois fronts : Réglage du noyau/VM (vm.min_free_kbytes, THP/Huge Pages), meilleurs modèles d'allocation (pools, pré-allocation, séparation des durées de vie) et gestion propre de l'exploitation (monitoring, désencombrement planifié, discipline NUMA). Je base mes diagnostics sur /proc/buddyinfo, le compteur de compression et la mesure du plus grand bloc libre, car les sommes de RAM sont trompeuses. Je fais explicitement attention à la virtualisation et aux hyperviseurs, afin que l'invité et l'hôte ne travaillent pas l'un contre l'autre et que de grandes quantités de données ne soient pas perdues. Blocs sont réservés à l'avance. En combinant ces éléments, on augmente la prédictibilité, on évite les pannes dues à l'OOM et on fournit des réponses plus rapides, surtout lorsque le trafic et les données augmentent.

Derniers articles