...

Comment analyser correctement les statistiques NUMA sous Linux

Linux NUMA Les statistiques me permettent de voir dans quelle mesure les processus conservent leur mémoire en local et où les accès à distance augmentent les latences. J'explique comment j'interprète ces chiffres de manière ciblée, comment j'évalue les tendances au fil du temps et comment j'en déduis des mesures d'optimisation claires pour Performance de l'entreprise.

Points centraux

  • Comprendre les compteurs: numa_hit, numa_miss, numa_foreign, local_node, other_node, interleave_hit
  • Évaluer le contexte: profil de charge, topologie, type de charge de travail
  • Mesurer les tendances: Avant/après et par intervalles
  • Vérifier les processus: À l'échelle du système ou par processus
  • Appliquer le réglage: Affinité, Politiques, Placement

Ce que révèlent réellement les statistiques NUMA

Je considère les chiffres NUMA comme une carte pour Lieu de stockage et les voies de transmission des données. Un niveau élevé de numa_hit signifie que les allocations ont été effectuées sur le nœud souhaité. En revanche, numa_miss indique que le noyau a dû se rabattre sur un autre nœud. Le compteur numa_foreign indique la valeur correspondante sur le nœud cible et complète le tableau. Grâce à local_node et other_node, je peux déterminer si les accès sont restés locaux ou s'ils ont utilisé de la mémoire distante.

Ces chiffres ne doivent jamais être interprétés isolément, car Charges de travail réagissent de manière très différente. Les processus courts génèrent parfois des échecs, sans pour autant modifier sensiblement les performances globales. Les politiques d'intercalage, en revanche, génèrent délibérément des allocations réparties, ce qui fait augmenter le taux d'intercalage réussi (interleave_hit). Je vérifie donc toujours la politique prévue et la charge actuelle. Ce n’est qu’ensuite que je décide si une valeur nécessite une intervention ou si elle correspond à la conception.

Pour moi, l'idée centrale est la suivante : Compteur fournissent des signaux, pas des jugements. Je recherche des tendances dans le temps, pas des chiffres isolés. C'est ainsi que je détermine si une modification apportée au système modifie la localisation. Ce n’est qu’à partir de ce tableau des tendances que j’évalue s’il faut déplacer des processus, adapter les politiques ou définir des contraintes CPU. Chaque analyse NUMA commence donc par une problématique claire et des points de mesure reproductibles.

Interpréter les chiffres clés dans leur contexte

Je compare toujours numa_hit et numa_miss ensemble, plutôt que d'évaluer des valeurs absolues. Si les « misses » augmentent, je vérifie en parallèle l'évolution de numa_foreign sur les nœuds cibles potentiels. Si les deux concordent, cela indique un véritable déplacement et non un simple artefact de lecture. local_node et other_node complètent ce tableau en fournissant des informations sur les accès réels. Je peux ainsi déterminer si une allocation a certes commencé localement, mais si l’opération a ensuite lu davantage de mémoire distante.

Un seul grand other_node Cela ne me dérange pas que la charge de travail soit répartie de manière délibérée. En revanche, les serveurs web comportant de nombreux workers tirent profit d'une localisation cohérente. C’est pourquoi j’examine les processus individuellement, et pas seulement la vue d’ensemble. Dès que certains services sortent du rang, je m’attaque à leur emplacement. Ce n’est que lorsque les problèmes s’accumulent à l’échelle du système que je cherche des causes liées à la topologie ou à la charge.

Comparaisons dans le temps : une routine de mesure qui porte ses fruits

Je relève les compteurs au début et à la fin d'une phase finale et je calcule la différence. Les valeurs isolées masquent les tendances, tandis que les différences révèlent une évolution. Des intervalles répétés, par exemple de 30 à 60 secondes, suffisent souvent pour identifier des tendances. Après des déploiements, des mises à jour du noyau ou des modifications matérielles, je compare à nouveau ces mêmes intervalles. Si le nombre d’erreurs augmente ou si local_node se déplace, cela indique un véritable changement.

Ces séries chronologiques couvrent erreur de placement plus rapidement que des instantanés. Je mettrais en corrélation ces courbes avec la charge du processeur, les changements de contexte et l’utilisation de la mémoire par nœud. Cela me permet de voir si des goulots d’étranglement dans la mémoire vive d’un nœud entraînent des mouvements de contournement. Ou si de nouveaux processus font basculer l’équilibre des nœuds. Je présente toujours le simple ratio entre les succès et les échecs sous forme de courbe, et non comme un chiffre isolé.

Analyser l'ensemble du système, puis se concentrer sur les processus

Je commence par une vue d'ensemble à partir de numastat et je n'examine les processus individuels qu'ensuite. Cet ordre de procéder permet de gagner du temps, car de nombreux effets deviennent visibles à l'échelle globale. Pour la vue des processus, j'utilise la sortie spécifique au processus afin d'isoler les services qui se démarquent. Dès que les candidats sont identifiés, j'ajuste le Placement sur l'utilisation du processeur et de la mémoire. L'article consacré à ce sujet rassemble des conseils pratiques à ce sujet : Affinité CPU et mémoire.

C'est justement avec les services Java, PHP-FPM ou les bases de données qu'une simple Affinity, afin de réduire considérablement les erreurs. L'orchestration des conteneurs a tendance à masquer ces problèmes, car les planificateurs répartissent les tâches sans tenir compte de la structure NUMA. Je contrôle donc l’affectation des nœuds par pod ou par machine virtuelle. Lorsque les ensembles de processeurs et l’allocation de RAM correspondent, la valeur de `local_node` augmente de manière mesurable. Certains problèmes se résolvent dès que le processus s’exécute à proximité de l’ensemble de données requis.

Aperçu des compteurs NUMA (tableau)

Je remplis le tableau suivant lorsque j'évalue un nouveau système, afin de pouvoir examiner chaque Chiffre clé Je l'analyse rapidement. Elle indique la signification, l'interprétation type et les mesures possibles. Je ne la considère pas comme un schéma rigide, mais plutôt comme une liste de contrôle. La comparaison avec le profil de charge et la topologie du serveur reste déterminante. Ce n'est qu'en tenant compte de ce contexte que je peux prendre une décision pertinente.

Compteur Signification interprétation Approche
numa_hit Allocation au nœud souhaité Une valeur élevée est positive Conserver le classement
numa_miss L'allocation s'est reportée sur d'autres nœuds Risque accru de latences Vérifier l'affinité/la politique
numa_foreign Allocation externe sur ce nœud Contrepartie de numa_miss Analyser les nœuds cibles
local_node Accès à la mémoire locale Plus c'est haut, moins ça coûte cher Processus plus proche de la mémoire RAM
other_node Accès à la mémoire distante Simplement préoccupant, sans intention particulière Vérifier la topologie/la charge
interleave_hit Résultats pour la répartition par entrelacement Attendu avec la politique d'entrelacement Évaluer l'uniformité

Avec cette Aperçu Je décide plus rapidement quand intervenir. Une augmentation de « numa_miss » sans changement explicable déclenche une analyse des causes. Si les valeurs d’interleave_hit restent élevées, je vérifie si la politique est activée comme prévu. Si other_node augmente sans augmentation de la charge, je vérifie s’il y a des charges de travail qui prennent le pas. Le tableau devient ainsi le point de départ de mesures ciblées.

Comprendre et exploiter la topologie NUMA

Avant de procéder au réglage, je vérifie les Topologie du serveur : sockets, cœurs, canaux de mémoire, chemins de latence. Si un processus s'exécute sur le socket 0 mais que ses blocs de travail se trouvent sur le socket 1, le temps d'accès augmente. Cela réduit le débit et entraîne des fluctuations des temps de réponse. Les services particulièrement gourmands en mémoire ressentent chaque distance inutile. C'est pourquoi j'affecte les processus gourmands en données à des nœuds disposant de suffisamment de RAM libre.

Asymétrique Connexions renforcent les effets, par exemple lorsqu’un nœud utilise moins de canaux. Dans de tels cas, je déplace de manière ciblée les données mises en cache, plutôt que de répartir le processus. Je configure les hôtes de machines virtuelles et de conteneurs de manière à ce que chaque instance bénéficie d’une liaison cohérente avec un nœud. Je réduis ainsi le trafic de données à distance sans limiter les quotas. Les contraintes physiques de la machine fixent les limites, et je m’y tiens.

Bien comprendre l'entrelacement et l'équilibrage

Les politiques d'interleaving répartissent délibérément la mémoire entre les nœuds afin que Débit augmente par processus ou diminue au niveau des points chauds. Dans cette configuration, des valeurs élevées d'`interleave_hit` sont considérées comme souhaitables. Je vérifie alors avant tout l'uniformité, et non la localité absolue. AutoNUMA ou le rééquilibrage NUMA peuvent aider, mais pas dans toutes les situations.

Je décide au cas par cas si l'option automatique Équilibrage reste actif. Pour les services cohérents et de longue durée, je préfère utiliser des liaisons fixes. En cas de charges variables, AutoNUMA peut s'avérer utile. L'article suivant offre un bon aperçu des avantages et des risques : Équilibrage NUMA. Ce n'est qu'une fois que l'objectif et le cadre sont clairement définis que je choisis le mode approprié.

Types de charges de travail : bases de données, machines virtuelles, services Web

Les bases de données sont sensibles à Latence entre le processeur et la mémoire vive. Je conserve donc l'instance, le cache tampon et les shards actifs sur le même nœud. Les machines virtuelles tirent parti d’ensembles de CPU bien définis associés à la mémoire vive du nœud, afin que les systèmes d’exploitation invités disposent de chemins d’accès cohérents. Les services web comportant de nombreux workers fonctionnent de manière optimale lorsque les groupes de workers restent liés à un seul nœud. Pour la stratégie de stockage, j’utilise, selon les cas, des Politiques de mémoire NUMA.

En revanche, pour les tâches analytiques et les analyses de grande envergure, je travaille en partie réparti . Dans ce cas, l'interleaving offre souvent de meilleures bandes passantes que la localité stricte. Il reste important d'analyser objectivement les modèles d'E/S de la charge de travail. Les écritures prédominent par rapport aux lectures, tout comme les accès aléatoires par rapport aux accès séquentiels. Je choisis la stratégie qui correspond au modèle d'accès, et non celle qui semble la plus pertinente dans les manuels.

Procédure de mesure pratique et outils

Pour commencer, cela me suffit numastat et la vue du processus. Je consigne les relevés de compteurs avec la date, le PID et les indicateurs de charge. Il est important de procéder aux mesures sur des plages horaires identiques. Cela permet de mettre clairement en évidence les différences « avant/après ». Pendant les fenêtres de production, je note les écarts et je les mets en corrélation avec les dates de mise en production.

En cas de Services Je vérifie également l'affectation du processeur et la visibilité des nœuds à l'aide d'outils tels que lscpu, numactl et perf pour évaluer la charge à distance. Je consigne la politique choisie à chaque cycle de mesure. Après chaque modification, je procède à une nouvelle mesure. Ce n’est que lorsque les courbes de tendance se stabilisent que je considère l’effet comme réussi. Changer de politique à l’aveuglette conduit facilement à des améliorations illusoires.

Éviter les interprétations erronées courantes

Un haut interleave_hit Ce n'est pas une erreur si l'interleave est activé intentionnellement. De même, un seul échec est négligeable sur une longue durée d'exécution. Je vérifie toujours la densité et la répartition sur l’intervalle, et pas seulement les pics. Certains interprètent « other_node » comme un indicateur globalement négatif et négligent la nature de la charge de travail. C’est pourquoi j’examine d’abord l’objectif de conception, puis j’évalue les chiffres.

Une autre erreur : Vue d'ensemble Bon, tout va bien. Souvent, les valeurs aberrantes ne se cachent que dans certains PID. Ou bien les planificateurs de conteneurs répartissent les pods sur plusieurs nœuds, alors qu’un regroupement local serait plus judicieux. Je ne remarque ces effets que lorsque je mesure au niveau de chaque processus. Sans cette précision, l’analyse reste incomplète.

Étapes de réglage efficaces

Je commence par Placement: Processus sur les nœuds où se trouvent ou doivent se trouver les données. Je configure ensuite l'affinité CPU afin que les threads ne passent pas d'un socket à l'autre. Je définis ensuite la liaison mémoire afin que le noyau alloue la mémoire à l'emplacement souhaité. Pour les charges variables, je vérifie les politiques et, si cela convient, j'active AutoNUMA.

Ensuite, je m'occupe de Consistance Au cours du cycle de vie : les redémarrages, les déploiements et les mises à l'échelle ne doivent pas modifier les références aux nœuds de manière aléatoire. Je documente les liaisons sous forme de code afin qu’elles restent reproductibles. Ensuite, je procède à de nouvelles mesures, j’évalue les écarts et je décide des ajustements à apporter. Chaque modification doit s’accompagner d’une preuve claire issue des mesures.

Exemple concret : de l'échec au succès

Supposons qu'une Base de données affiche, sous charge, une valeur plus élevée pour `numa_miss` et une augmentation de `other_node`. La latence des requêtes varie davantage. Je vérifie d’abord l’affectation des processus et constate qu’après un déploiement, le service s’exécute sur le nœud A, mais que le cache a été alloué au nœud B. Après avoir attribué de manière fixe le processeur et la mémoire au nœud B, le rapport s'inverse : numa_hit augmente, tandis que les « misses » diminuent. Les temps de réponse deviennent plus constants et la charge du processeur baisse légèrement, car les accès à distance disparaissent.

En parallèle, je vérifie les Politique. La fonction « Interleave » était activée par inadvertance et a réparti les allocations. Après le passage au nœud privilégié, le cache reste localement fermé. Après une heure de mesure, les écarts confirment l'amélioration. Ce n'est qu'à ce moment-là que je considère ce réglage comme une réussite. Sans cette vérification croisée, un instantané aurait pu induire en erreur.

Indicateurs et valeurs de référence pour la pratique

Pour prendre des décisions, je m'appuie sur des Cotes plutôt que des valeurs brutes individuelles. Je calcule, pour chaque processus, le taux d'allocation local_alloc = numa_hit / (numa_hit + numa_miss). De plus, j'évalue les Taux d'accès local_access = local_node / (local_node + other_node). Ces deux valeurs indiquent si la mémoire reste utilisée localement même après l’allocation. Voici les valeurs indicatives que j’utilise : pour les services où la latence est critique, je vise des accès à distance inférieurs à 5–10 %. Pour les charges de travail analytiques en bande passante, je tolère 20 à 30 %, à condition que le débit augmente. Ce qui est déterminant, c’est la Stabilité au fil du temps. Je préfère un taux qui reste stable sous charge à un bref pic affichant des valeurs parfaites. Je consigne ces fourchettes cibles pour chaque intervention, afin de pouvoir replacer clairement les mesures ultérieures dans leur contexte.

Cgroups, conteneurs et pièges liés au planificateur

Dans les environnements de conteneurs, je vérifie d'abord la cpuset‑Affectation : ensembles de processeurs et cpuset.mems doivent correspondre au même espace de nœuds, sinon des erreurs surviennent inévitablement. Je m’assure que les pods ayant des demandes de CPU fixes ne s’étendent pas sur plusieurs nœuds NUMA, et que le planificateur ne répartisse pas les workers d’une même application de manière dispersée. Pour les types « burst », je limite le nombre maximal de threads par pod de manière à ce qu’ils restent au sein d’un même nœud. Je documente les Domaine NUMA par déploiement et exige des répliques cohérentes (un groupe de travailleurs par nœud, pas de demi-groupes répartis sur deux nœuds). Si je calcule au plus juste la taille de mémoire par pod, je me mets involontairement sous pression : une légère marge par nœud évite que le noyau ne doive se rabattre prématurément sur d’autres nœuds. Si les conteneurs sont fréquemment redémarrés, je veille à ce que la liaison soit déterministe, afin que Départs à froid ne se retrouve pas par hasard dans un endroit moins favorable.

Aligner les machines virtuelles et vNUMA de manière cohérente

En ce qui concerne les machines virtuelles, je me concentre sur vNUMA: La topologie virtuelle doit correspondre à la topologie physique. Je répartis les vCPU de manière à ce que chaque nœud vNUMA corresponde exactement à un nœud NUMA physique. Côté hôte, j’affecte les threads QEMU/hyperviseur à ce domaine et je m’assure que la mémoire RAM allouée provienne intégralement de ce nœud. Ballooning Quant à l’overcommit, je ne l’utilise qu’avec prudence pour les machines virtuelles sensibles à la latence ; un ballooning agressif peut évincer les hotsets du nœud et faire grimper le nombre d’accès à distance. En cas de migration à chaud, je vérifie à nouveau les affectations après le déplacement : certains environnements perdent en effet les affectations de CPU et de mémoire finement configurées. Ce n’est que lorsque l’alignement vNUMA est correct que j’évalue numastat à l’échelle du système : sinon, je ne fais que réparer les symptômes, et non la cause.

THP, Hugepages et migration de pages

Pages transparentes volumineuses (THP) peuvent à la fois aider et gêner. Des pages plus volumineuses réduisent les échecs de TLB et améliorent la bande passante, mais si le noyau n'active les Hugepages que tardivement s'est effondré ou migrés, peuvent entraîner des Distances lointaines apparaissent. Je respecte deux règles : premièrement, la Politique définir clairement (par exemple, nœud préféré) et, dans la mesure du possible, allouer dès le départ des blocs de mémoire importants au niveau local. Deuxièmement, pour les charges de travail avec des caches fixes et volumineux, je configure si possible des « hugepages » statiques (hugetlb) que je réserve explicitement sur un nœud. Cela réduit la fragmentation et les mouvements de contournement liés à la compensation. Si je constate dans les séries chronologiques qu’après une durée d’exécution prolongée, le other_node- Si cette part augmente, je vérifie si Migration de pages ou s'il y a compaction, et si les paramètres THP correspondent au modèle. Ce qui m'importe, c'est de ne pas activer ou désactiver ces paramètres de manière globale : je prends ma décision au cas par cas, pour chaque service, et j'évalue l'impact sur la localité et la latence.

Bien comprendre les notions de « Reclaim », « Swap » et « Memory-Pressure »

Lorsque les juments montent sans changement apparent de placement, je cherche à déterminer pression de stockage par nœud. Les nœuds pleins obligent le noyau à effectuer des opérations de récupération d'espace et de compactage, en partie déclenchées par kswapd sur un autre nœud – cela entraîne des effets secondaires au niveau des compteurs. Je vérifie, pour chaque nœud, la mémoire libre et le taux d'utilisation du cache de pages. Activé Swap peut évincer les hotsets et faire exploser les latences ; pour les services particulièrement sensibles, je désactive le swap ou je le limite strictement. Dans les journaux et les vues perf, je recherche les pics de « Reclaim » pendant les pics de charge. L'objectif est de disposer de suffisamment d'espace libre, locaux Conserver la RAM sur le nœud cible afin d'éviter toute dérive des allocations. Si cela nécessite de réduire la taille des caches, je donne la priorité au jeu de travail du service par rapport au cache de pages générique.

Commandes pratiques et analyse

Pour la vue « processus », j'utilise numastat -p et j'ajoute : cat /proc//numa_maps, pour afficher les allocations par domaine (anonymes, basées sur des fichiers) et par nœud. numactl --hardware me fournit des matrices de latence et des tailles de nœuds, lscpu --extended affiche l'affectation des processeurs aux nœuds. Pour les accès mémoire axés sur les chemins distants, je définis mémoire de performance pour vérifier les profils de charge. Je collecte les deltas de manière reproductible, par exemple :

Routine de mesure

  • t0 : sauvegarder numastat (complet) et numastat -p pour les PID les plus importants
  • 30 à 60 s d'effort, phase de charge identique
  • t1 : relire numastat, calculer les deltas par compteur
  • Enregistrer les valeurs parallèles de la mémoire du processeur, des changements de contexte et des nœuds

Je calcule ensuite la Cotes et je repère les processus qui s’écartent de manière significative des tendances à l’échelle du système. En cas de doute, je répète la mesure au moins trois fois. Je ne considère comme fiables que les écarts cohérents. Pour une surveillance continue, je mappe les compteurs sur des séries chronologiques et je les associe aux métadonnées de version – cela me permet d’identifier Points de régression immédiatement.

Liste de contrôle pour un réglage NUMA méthodique

  • Définir l'objectif : latence vs débit, charge fixe vs charge variable
  • Recenser la topologie : nœuds, latences, réserves de mémoire RAM disponibles par nœud
  • Mesurer la situation de référence : numastat global et par processus, calculer les ratios
  • Corriger le placement : affinité CPU, liaison mémoire, politiques
  • Configurer les conteneurs/machines virtuelles : cpuset.cpus = nœuds, cpuset.mems adapté ; mappage vNUMA correct
  • Choisir judicieusement THP/Hugepages, en gardant un œil sur la fragmentation
  • Réduire la pression sur la mémoire : marge de mémoire par nœud, vérifier la stratégie d'échange
  • Vérification a posteriori : comparer les deltas, garantir la stabilité dans le temps
  • Documentation : liaisons sous forme de code, notes de mise à jour avec contexte NUMA

En bref

J'analyse les données NUMA en Signaux À lire dans ce contexte : paires de compteurs, évolutions temporelles et vue des processus. Les valeurs clés numa_hit, numa_miss, numa_foreign, local_node, other_node et interleave_hit me renseignent sur la localité, les mouvements d'évitement et les stratégies de répartition. Je prends mes décisions en fonction de la topologie et de la charge de travail, et non sur la base de seuils rigides. L'optimisation commence par le placement, l'affinité, une politique adaptée et une routine de mesure rigoureuse. C'est ainsi que j'assure une performance constante Performance, car le processeur et la mémoire vive sont adaptés à l'application et les transferts de données sur de longues distances restent rares.

Derniers articles

Infrastructure technique des serveurs, avec un accent particulier sur l'analyse NUMA sous Linux
Serveurs et machines virtuelles

Comment analyser correctement les statistiques NUMA sous Linux

Analyser correctement les statistiques NUMA sous Linux : comprendre les statistiques NUMA, vérifier la localité de la mémoire et améliorer de manière ciblée les performances du serveur.