...

Optimiser l'affinité du processus serveur et la sensibilisation au NUMA dans l'hébergement

J'augmente les performances du serveur en Affinité de processus et NUMA-Awareness, afin d'optimiser l'agencement des threads, des cœurs et de la mémoire. Cela me permet de réduire les temps de latence, d'augmenter le débit et d'obtenir des temps de réponse réguliers dans les environnements d'hébergement avec de nombreuses applications.

Points centraux

Avant de définir des paramètres concrets, je clarifie les objectifs, les modèles de charge de travail et la topologie matérielle existante. J'analyse quels threads sont particulièrement gourmands en mémoire et quels processus nécessitent des temps de réaction courts. Je tiens compte du nombre de cœurs disponibles par nœud NUMA et de la quantité de RAM locale qui s'y trouve. Je prévois de regrouper les services par nœud pour que Localité de l'unité centrale est préservé. Je mesure chaque changement à l'aide de benchmarks et de monitoring afin d'éviter toute erreur d'hypothèse.

  • AffinityLier des processus à des groupes de base
  • NUMA: garder la mémoire en local
  • Topologie: mise à l'échelle nœud par nœud
  • Suivi: rendre les accès à distance visibles
  • Hébergement: Contrôler le placement de l'hyperviseur

Que signifie Process Affinity sur le serveur ?

Avec Affinité de processus je détermine sur quels cœurs de l'unité centrale un processus ou un thread s'exécute, au lieu de laisser le système d'exploitation décider librement. Cela permet de maintenir la cohérence du contenu du cache, ce qui réduit les échecs de cache et les changements de contexte. J'épingle les threads de manière à ce qu'ils utilisent efficacement leurs caches L1/L2/L3 et ne sautent pas entre les noyaux. Cela renforce la prévisibilité des latences en cas de charge élevée et assure une utilisation régulière des cœurs réservés. Pour une initiation pratique, ce guide m'aide à Affinité CPU dans l'hébergement, J'ai utilisé cette fonction pour comparer des variantes typiques de l'épinglage.

Comprendre la NUMA : accès local vs. accès à distance

NUMA divise la mémoire en nœuds, chacun d'entre eux étant étroitement lié à des socles de CPU spécifiques. Un thread accède plus rapidement à la RAM locale qu'à la mémoire distante d'autres nœuds. Cette asymétrie a un impact considérable sur les charges de travail réelles, en particulier lorsque le nombre de cœurs et l'extension de la RAM sont élevés. C'est pourquoi j'attribue les threads et leurs accès à la mémoire à un nœud commun, afin de réduire les latences et d'augmenter la bande passante. Ceux qui souhaitent approfondir la topologie trouveront des informations pratiques sur les points suivants Nœuds NUMA dans le serveur et mesure ensuite les effets dans la vie quotidienne.

Sensibilisation à la NUMA dans le système d'exploitation et l'application

J'active Sensibilisation à la NUMA dans le système d'exploitation, l'hyperviseur et l'application, afin que la mémoire soit allouée localement. Dans la mesure du possible, je garde les threads d'une instance sur les cœurs du même nœud NUMA au lieu de les répartir sur les nœuds. Je préfère placer les gros tas ou tampons dans la RAM locale afin de limiter les accès distants coûteux. Si une application possède plusieurs travailleurs, je les structure en pools, nœud par nœud, afin d'éviter les interférences. Il en résulte une affectation claire de l'unité centrale et de la mémoire, ce qui réduit sensiblement les temps de réponse.

Interaction entre Affinity et NUMA

Affinity sans planification NUMA gaspille le potentiel si la mémoire se trouve sur des nœuds éloignés. De même, la prise en compte de la NUMA n'apporte pas grand-chose si l'ordonnancement déplace souvent les threads. C'est pourquoi je lie les threads aux noyaux d'un nœud concret et je veille en parallèle à l'allocation locale de la mémoire. Si je fais évoluer l'application, je remplis d'abord un nœud avant d'inclure d'autres nœuds. Ce couplage de l'épinglage des noyaux et de la politique de mémoire génère des profils de latence constants sous la charge.

Réglage du matériel et du firmware (UEFI/BIOS)

Pour que Affinity et NUMA soient efficaces, je règle la base de manière stable dans le firmware. Je privilégie des modes de performance cohérents plutôt que des options d'économie d'énergie agressives, afin de minimiser les fluctuations de la fréquence et de la latence. Points importants que je vérifie :

  • Profil de performance : performance maximale/performance au lieu de Balanced ; limiter les C-States bas si la latence est plus critique que l'efficacité.
  • Stratégie turbo/boost : boost déterministe à la demande pour éviter les noyaux P fluctuants.
  • SMT/Hyper-Threading : tester en fonction de la charge de travail - pour les SLA de latence dure, j'épingle souvent les threads critiques sur des cœurs physiques et sépare les jumeaux SMT.
  • Entrelacement de la mémoire : désactivé lors de l'optimisation NUMA, afin que les nœuds restent bien délimités.
  • Canaux de mémoire : équipement symétrique des slots DIMM par nœud pour une bande passante maximale.

Chemin de configuration : de l'analyse à l'épinglage

Je commence par un enregistrement de la topologie, typiquement avec lscpu, numactl -hardware ou hwloc. Ensuite, je définis pour chaque service le nombre de cœurs nécessaires et je les attribue à un nœud. Je réalise l'épinglage à l'aide de taskset ou des options Systemd afin que l'affectation reste reproductible. Lors du test, j'adapte la taille des groupes de noyaux jusqu'à ce que la latence et le débit soient en bon équilibre. Ce faisant, je veille à ce qu'aucun service nécessitant beaucoup de CPU ne partage le même pool de noyaux et ne supplante ainsi les caches des autres.

Sous Linux, j'aime définir l'affinité et la politique de mémoire de manière déclarative via cgroups (v2) : Je définis cpuset.cpus et cpuset.mems par nœud et je lance des services avec des paramètres Systemd comme CPUAffinity= et NUMAMask=. Pour les processus par lots ou secondaires, je prévois des pools séparés afin qu'ils n'atteignent pas les cœurs des animaux à latence critique. Pour les tâches récurrentes, je planifie des fenêtres de démarrage exactes dans lesquelles les cœurs sont libres.

Affinité d'interruption et d'E/S

Il n'y a pas que les fils d'apps qui ont besoin de Locality - il y a aussi Interruptions et les chemins d'E/S, je les ordonne à proximité des nœuds :

  • Réseau : lier les queues RX/TX d'une carte réseau aux cœurs du même nœud NUMA (configurer RSS/XPS), afin que le traitement des paquets et les threads d'applications partagent la localité du cache et de la RAM.
  • Stockage : épingler les files NVMe et les threads IO par nœud ; pour blk-mq, vérifier la répartition des files afin d'éviter que les volumes chauds ne traversent les nœuds.
  • irqbalance : soit la configurer de manière ciblée, soit la désactiver pour les files d'attente critiques et la définir manuellement par smp_affinity.

Utiliser les fonctions du système d'exploitation de manière ciblée

Pour des profils de latence stricts, j'utilise délibérément des fonctionnalités du noyau :

  • isolcpus/nohz_full/rcu_nocbs : découpler les noyaux de l'ordonnancement général, minimiser la charge des ticks et déplacer les rappels RCU - idéal pour les threads à haute perf.
  • Politiques de l'ordonnanceur : pour les parts en temps réel, utiliser SCHED_FIFO/RR avec parcimonie ; sinon CFS avec affinité étroite.
  • Auto NUMA Balancing : souvent désactivé pour les charges de travail strictement épinglées, afin que le noyau ne déplace pas la mémoire.
  • Transparent Huge Pages : mettre la plupart du temps sur madvise et utiliser des Huge Pages explicites pour les très gros tas afin de réduire les TLB miss.

Politique de stockage consciente de la NUMA

Avec numactl j'impose une allocation locale préférentielle de la mémoire ou j'utilise des politiques telles que preferred et interleave. Dans la mesure du possible, je garde les grandes structures en mémoire, comme les pools de tampons d'une base de données, au sein d'un nœud. Si le besoin de mémoire se déplace, j'observe l'augmentation des accès à distance et je réagis par la segmentation ou le sharding. Les directives relatives à l'utilisation de l'espace de stockage me donnent un aperçu pratique du réglage. NUMA-Balancing, que je confirme ensuite par des tests de charge. Ainsi, le temps d'accès à la mémoire reste faible et prévisible.

techniques de stockage : Huge Pages, Heaps et Garbage Collection

La gestion de la mémoire détermine souvent les latences du P99. J'utilise les Huge Pages là où les grands tas à longue durée de vie dominent (par exemple les buffers DB, les tas JVM). Cela réduit les TLB miss et les Pagewalks. Pour les charges de travail JVM, je tiens compte de la taille des tas par nœud et j'active l'optimisation NUMA pour que les threads GC et les tas restent locaux. Pour .NET et Go, je planifie les GC et les pools de goroutine de manière à ce qu'ils ne remplissent pas les cœurs de manière incontrôlée sur les nœuds. Dans les bases de données, je divise les grands pools de tampons en segments locaux aux nœuds ou j'exécute plusieurs petites instances par nœud.

Pratique de l'hébergement : charges de travail typiques

Les bases de données, les caches et les grands serveurs d'applications sont sensibles aux Localité de l'unité centrale et la latence de la mémoire. Une VM répartie sur plusieurs nœuds NUMA augmente les chemins de calcul et de mémoire et ralentit les requêtes ou les appels API. Je place donc les VM de telle sorte que leurs vCPU soient affectés à un nœud physique et que la mémoire y reste. Les pools de conteneurs reçoivent des ensembles de CPU cohérents, afin que les travailleurs ne sautent pas par-dessus les nœuds. Ce soin est particulièrement payant pour le commerce électronique et les services API qui nécessitent un parallélisme élevé.

Stratégies d'applications à granularité fine

Au niveau de l'application, je découple les nœuds afin de préserver la localité :

  • Des pools de travailleurs : Un pool par nœud NUMA, chacun avec une file d'attente locale afin d'éviter les communications inter-nœuds.
  • Sharding : garder les données et les sessions locales aux nœuds ; choisir le hachage de manière à ce que les hot shards ne croisent pas plusieurs nœuds.
  • Caches : répliqués plutôt que centralisés ; les lecteurs préfèrent les copies node-local.
  • Thread pinning dans les runtimes : pour les piles réseau (par ex. Netty) et les clients DB, lier les worker à des noyaux fixes, tenir compte de la proximité de l'IRQ.

Monitoring et recherche d'erreurs

Un monitoring judicieux montre plus que l'utilisation globale, car NUMA-Les effets se cachent dans les valeurs détaillées des nœuds. J'observe la charge CPU par cœur et par nœud, l'utilisation de la mémoire par nœud et les taux d'accès à distance. Si certains cœurs débordent alors que d'autres restent inutilisés, cela indique de mauvaises configurations d'affinités. Si un nœud se remplit de RAM alors qu'un autre a de la réserve, je dois adapter la politique de mémoire ou le placement. Ces signaux me permettent de prouver objectivement l'existence de goulots d'étranglement et d'en déduire les prochaines modifications.

Métriques Remarque/symptôme Cause typique Action rapide
CPU par cœur Quelques noyaux élevés en permanence Mauvais épinglage Redistribuer les groupes de base
RAM par nœud Un nœud dans la limite Mémoire non locale mettre numactl preferred
Taux à distance Accès à distance élevé VM/conteneur sur les nœuds Regrouper un ensemble vCPU/CPU
Commutateurs contextuels Latence intermittente Randonnée du fil de discussion Épingler Affinity plus fort

Anti-patterns et pièges typiques

J'évite les limites globales de CPU sans tenir compte de NUMA, car elles allouent des noyaux à travers les nœuds. De même, „One big VM“ avec trop de vCPUs évolue rarement de manière linéaire - mieux vaut plusieurs instances locales aux nœuds. Transparent Huge Pages en mode Always provoque parfois des pics de défaut de page ; madvise plus Huge Pages ciblées est plus prévisible. Laisser irqbalance s'exécuter de manière incontrôlée dilue la localité I/O. Et : un pinning trop dur sans noyaux tampons peut étouffer la maintenance et la charge secondaire - je prévois toujours quelques noyaux libres par nœud.

Rendre les effets de la performance mesurables

Je mesure des effets de Affinity et les modifications NUMA toujours avec des benchmarks reproductibles. Les comparaisons avant/après avec un ensemble de données identiques montrent les améliorations de manière transparente. Je combine des tests synthétiques avec des profils de charge réalistes pour que les optimisations portent leurs fruits au quotidien. Les indicateurs de résultats tels que les latences P95 et P99 sont souvent plus parlants que les valeurs moyennes. Ainsi, je peux prendre des décisions plus sûres et identifier les effets secondaires à un stade précoce.

Virtualisation et conteneurs

Dans les configurations d'hyperviseur, je place vNUMA, pour que la VM invitée comprenne la topologie physique. Je place les vCPU d'une VM dans un nœud physiquement adapté afin de minimiser les accès à distance. Pour les conteneurs, je définis les demandes de CPU et les limites de manière à ce que les ensembles de CPU restent cohérents et que le gestionnaire de topologie respecte la localisation des nœuds. Je n'empile les grandes VM avec de nombreuses vCPU sur les nœuds que si l'application autorise la segmentation en interne. J'évalue chaque placement en fonction de la latence, du débit et de la charge par nœud.

Orchestration : Cgroups, Kubernetes et autres.

Dans les conteneurs, je mise sur les classes garanties ou en rafale avec des ensembles de CPU stables et l'affectation de mems. Le gestionnaire de topologie en mode „single-numa-node“ aide à garder les pods locaux aux nœuds. Pour les parties en temps réel de longue durée, j'utilise le gestionnaire de CPU en mode „static“ pour que les noyaux restent exclusifs. Je planifie les HugePages comme requêtes/limites et je regroupe les pods par rôle de charge de travail afin que les nœuds ne soient pas surchargés de manière hétérogène. Important : gérer proprement les étiquettes de nœuds afin que les règles de placement Locality ne soient pas brisées involontairement.

Rôle du fournisseur d'hébergement

Un bon fournisseur fournit des informations transparentes Topologie NUMA, options d'affinité et visibilité des métriques de nœuds. Je veille à ce que l'hyperviseur et l'orchestration prennent la NUMA-Awareness au sérieux et que le placement vCPU reste contrôlable. Il est également important de disposer d'un monitoring qui fournisse le CPU, la RAM et les quotas à distance par nœud. Je peux ainsi décider moi-même de la rigueur de mes pins et de la manière dont je définis les politiques de mémoire. Ce contrôle rend les charges de travail exigeantes fiables et planifiables.

Modèle d'exploitation : introduire des changements en toute sécurité

J'introduis les politiques d'épinglage et de NUMA de manière itérative : d'abord sur un nœud, avec des étapes de retour en arrière clairement définies. Je documente la topologie, les affectations et les paramètres du noyau afin d'assurer la reproductibilité. Pour les versions, j'utilise le trafic Canary, j'observe les P95/P99, les commutateurs contextuels et les taux à distance pendant au moins une phase de pleine charge et je ne déploie plus largement qu'ensuite. Ainsi, les améliorations restent stables et les risques sont gérables.

Les meilleures pratiques, appliquées de manière compacte

Je commence chaque optimisation par un examen approfondi Analyse de la topologie et je documente l'allocation du noyau et des nœuds. Ensuite, je répartis les charges de travail de manière à ce que la base de données, le cache et le serveur d'applications reçoivent des ressources de nœuds séparées. J'épingle les processus critiques et règle la mémoire de préférence en local avant d'ajuster finement la taille des groupes. J'accompagne chaque réglage de benchmarks et de métriques de nœuds afin de voir clairement les effets. Pour la croissance, je planifie nœud par nœud et je garde les instances légères au lieu de gonfler une instance monolithique géante.

Résumé et prochaines étapes

Avec une approche ciblée Affinité de processus et une véritable prise de conscience de la NUMA, je fais progresser sensiblement les charges de travail sur le même matériel. Un placement clair, une allocation locale de la mémoire et une mesure cohérente des résultats sont décisifs. En regroupant les VM et les conteneurs à proximité des nœuds, on réduit la latence et on augmente le débit. Je recommande de lancer un projet pilote sur un hôte, de tester l'affinité et la politique de mémoire et d'adopter les meilleurs paramètres. Ainsi, les performances augmentent petit à petit, sans avoir à acheter de nouveaux serveurs.

Derniers articles