L'architecture NUMA révolutionne la gestion de la mémoire dans les serveurs modernes, offrant évolutivité mais introduisant de nouveaux défis de performance. Découvrez comment le placement des données et la topologie CPU influencent la latence, l'efficacité et la stabilité, ainsi que les meilleures pratiques pour éviter les pièges courants de NUMA.
L'architecture NUMA (Non-Uniform Memory Access) est devenue un élément incontournable dans le monde des serveurs modernes. Contrairement aux anciens modèles où chaque processeur accédait à la mémoire de façon uniforme, NUMA introduit une nouvelle dynamique : le temps d'accès à la mémoire dépend désormais de la localisation physique des données par rapport au cœur du processeur. Ce changement architectural, bien qu'il vise à améliorer la performance et l'évolutivité, peut paradoxalement entraîner des pertes d'efficacité et compliquer la gestion des serveurs sous charge.
À l'origine, les ordinateurs utilisaient le modèle UMA (Uniform Memory Access), où un ou plusieurs cœurs accédaient à un contrôleur de mémoire central avec une latence identique. Ce modèle fonctionnait tant que le nombre de cœurs et la quantité de mémoire restaient modestes. Mais l'essor des serveurs multicœurs et multisocket a révélé les limites de l'UMA, avec le contrôleur de mémoire devenant un goulot d'étranglement.
NUMA a été introduite pour résoudre ce problème. Le serveur est divisé en plusieurs nœuds NUMA, chacun comprenant un processeur (ou un groupe de cœurs) et sa mémoire locale. L'accès à la mémoire locale est rapide, tandis que l'accès à la mémoire d'un autre nœud, via des bus inter-processeurs comme QPI ou UPI, induit une latence accrue.
Sur le plan matériel, cela améliore l'évolutivité et la rapidité d'accès à la mémoire, ce qui explique pourquoi NUMA est devenu le standard sur les plateformes serveur modernes, notamment en configuration multi-processeur.
La distinction majeure entre UMA et NUMA réside dans la gestion de la mémoire. En UMA, tous les cœurs accèdent à une mémoire partagée avec la même latence, ce qui simplifie le modèle de programmation et garantit une performance prévisible.
Avec NUMA, la mémoire reste théoriquement partagée, mais elle est physiquement répartie entre les nœuds. Les accès locaux sont bien plus rapides que les accès distants, pouvant entraîner des différences de latence de plusieurs dizaines de pourcents. Ce phénomène est particulièrement critique pour les applications sensibles à la latence, telles que les bases de données et la virtualisation.
En UMA, la dégradation des performances sous charge est progressive. Dans un environnement NUMA, la performance peut chuter brutalement dès qu'un flux ou des données sont déplacés sur un autre nœud. Cette imprévisibilité peut transformer un serveur puissant sur le papier en une machine instable en production.
Dans une architecture NUMA, chaque cœur de processeur est lié à un nœud NUMA possédant son propre contrôleur de mémoire et une portion de la RAM du serveur. L'accès local est direct et performant. Cependant, rien ne garantit que les données nécessaires à un processus se trouvent toujours sur le nœud local.
Si les données résident sur un autre nœud, leur accès passe par le bus inter-nœuds, ce qui augmente la latence et réduit la bande passante effective. Le système d'exploitation tente d'atténuer ce problème en plaçant la mémoire allouée sur le nœud où s'exécute le processus, mais cette optimisation n'est pas infaillible, surtout en cas de migration de processus ou de changements de charge.
Les caches processeur peuvent masquer le problème temporairement, mais sous forte charge, les accès distants deviennent fréquents, engendrant des délais supplémentaires et une performance globale en dents de scie.
La performance d'un serveur NUMA dépend étroitement de la topologie du processeur. Dans les serveurs multisocket, chaque socket constitue un nœud NUMA avec sa propre mémoire. Les sockets sont reliés par des bus rapides mais limités en latence et en bande passante.
Avec l'avènement des processeurs à conception chiplet, même un seul socket peut contenir plusieurs domaines NUMA internes, chacun avec un accès mémoire distinct. Le système d'exploitation doit gérer cette topologie complexe lors de l'attribution des ressources, mais en pratique, cette gestion reste souvent imparfaite.
L'ajout de sockets augmente la puissance formelle du serveur, mais introduit aussi de nouveaux niveaux de latence. Si l'application ne gère pas correctement la localité des données, l'augmentation des ressources matérielles peut se traduire par une hausse des surcoûts et une baisse de la performance réelle.
Le principal problème de NUMA est la perte de localité des données. Lorsque les processus et les données sont dispersés entre différents nœuds, chaque accès distant à la mémoire augmente la latence et réduit l'efficacité globale du serveur. Cela se traduit par des processeurs en attente de données, une baisse du nombre d'instructions par cycle (IPC), et une performance qui semble inexplicablement faible malgré une charge CPU élevée.
En outre, les connexions inter-nœuds deviennent des points de congestion. Plusieurs flux accédant simultanément à la mémoire distante peuvent saturer ce canal, entraînant une croissance non linéaire de la latence et des effondrements de performance sous forte charge.
La gestion des processus par le système d'exploitation n'est pas toujours adaptée à la réalité NUMA : pour équilibrer la charge CPU, un processus peut être déplacé sur un autre nœud sans que ses données suivent, aggravant ainsi la latence de chaque accès mémoire.
Les architectures NUMA révèlent pleinement leurs complexités dans les serveurs multiprocesseurs. Chaque socket supplémentaire multiplie les chemins possibles d'accès à la mémoire, rendant la gestion des ressources plus délicate.
Dans ces configurations, les connexions inter-nœuds sont cruciales : toutes les opérations de synchronisation, de gestion de cache et d'accès mémoire distant passent par ces canaux. Leur saturation peut limiter la performance bien avant que le CPU ou la RAM n'atteignent leurs limites théoriques.
L'ajout de processeurs n'offre pas toujours le gain attendu si l'application n'est pas conçue pour NUMA. Pire, plusieurs services ou machines virtuelles partageant le même serveur peuvent entrer en concurrence pour la mémoire des nœuds, générant des délais difficiles à diagnostiquer.
La synchronisation, via verrous ou opérations atomiques, peut entraîner le phénomène de " ping-pong " des lignes de cache entre sockets, réduisant la scalabilité et augmentant la latence.
Dans les systèmes NUMA, la latence d'accès à la mémoire devient le facteur déterminant de la performance. Même si la bande passante mémoire est suffisante, l'augmentation de la latence peut annuler tout bénéfice lié à l'ajout de cœurs ou de RAM.
Sur un flux unique, l'impact peut sembler mineur, mais avec des milliers de demandes simultanées, la latence s'accumule et provoque une dégradation rapide de la réactivité du serveur. Les systèmes fortement synchronisés, où les threads attendent souvent des données partagées, sont particulièrement vulnérables à ce problème.
La difficulté est exacerbée par le fait que les métriques classiques ne révèlent pas la source de la latence. La CPU et la mémoire semblent sous-utilisées, alors que la véritable limite provient des micro-délai d'accès mémoire non visibles dans les outils standards de monitoring.
NUMA n'est pas intrinsèquement un problème. Dans certains scénarios, elle est indispensable pour exploiter pleinement les capacités des serveurs multi-socket. Lorsque les calculs et les données sont bien localisés - calcul scientifique, rendu, certaines analyses - NUMA permet une scalabilité presque linéaire.
Pour les bases de données ou systèmes in-memory, NUMA peut être avantageuse si la répartition des données et l'affinité des threads sont soigneusement gérées. À l'inverse, les applications " universelles " (web, microservices, systèmes fortement synchronisés) souffrent davantage des aléas de NUMA, la performance devenant instable et imprévisible.
La virtualisation et la conteneurisation accentuent ces difficultés, les machines virtuelles pouvant perdre la localité des données et entrer en compétition pour la mémoire de différents nœuds.
NUMA pose particulièrement problème lorsque la latence minimale et stable est cruciale. Même si la performance moyenne semble acceptable, les pics de latence peuvent rendre le système inadapté à des applications critiques.
L'architecture NUMA a permis l'évolution des serveurs vers plus de cœurs et de mémoire, mais son adoption a introduit de nouveaux défis, souvent invisibles et difficiles à diagnostiquer. Le temps d'accès à la mémoire n'est plus constant, dépendant désormais de la topologie CPU et du placement des données.
Pour les applications et systèmes d'exploitation qui ignorent cette réalité, NUMA devient une source de latence, d'instabilité et de perte d'efficacité. Elle exige une gestion consciente de la localisation des processus et des données, ainsi qu'une compréhension approfondie de la topologie matérielle.
En définitive, NUMA transfère une part de la responsabilité de la performance du matériel vers le logiciel. Plus le serveur est puissant, plus le coût d'une erreur est élevé. Maîtriser NUMA n'est plus une option mais une nécessité pour garantir la stabilité et la prévisibilité des serveurs modernes.