Votre application ne s’est pas ralentie. Vos hypothèses l’ont fait. Si vous avez déjà vu une mise à niveau « simple » du CPU empirer la latence d’une base de données, ou noté qu’une flotte de VM se comporte comme si chaque hôte avait une personnalité différente, vous avez rencontré le contrôleur mémoire intégré (IMC) dans son habitat naturel : il façonne silencieusement les performances pendant que tout le monde discute du pourcentage CPU.
Le passage d’un contrôleur mémoire sur northbridge à un IMC était vendu comme un gain de vitesse. Il l’a apporté. Il a aussi changé les modes de défaillance, les réglages disponibles et les manières dont on peut se tromper avec des benchmarks. Ce n’est pas de la nostalgie. Voilà pourquoi vos graphiques de production ressemblent encore à ce qu’ils montrent.
Ce qui a réellement changé : du northbridge à l’IMC
Autrefois, le CPU ne parlait pas directement à la DRAM. Il communiquait avec un composant du chipset (le « northbridge ») via un front-side bus. Le northbridge parlait ensuite à la mémoire. Ce design avait une simplicité cruelle : tous les cœurs partageaient le même chemin vers la RAM, et la latence mémoire était en grande partie « un seul nombre » pour tout le socket.
Puis les fabricants ont commencé à intégrer le contrôleur mémoire dans le package CPU. Désormais, le CPU contient la logique qui pilote les DIMM : entraînement, timings, refresh, adressage, tout. Cela a fait trois choses majeures qui comptent encore en production :
- Latence plus faible et plus prévisible (moins d’étapes, moins de contention sur un bus partagé).
- Plus de bande passante via plusieurs canaux mémoire directement sur le CPU, évoluant par socket.
- La localité est devenue un concept de première classe : sur les systèmes multi-socket, chaque socket a sa mémoire attachée, et la mémoire « distante » est une toute autre affaire.
Du point de vue SRE, le changement le plus important n’était pas « c’est plus rapide ». C’était ceci : la mémoire est devenue une partie de la personnalité du CPU. Deux serveurs avec le même SKU CPU peuvent se comporter différemment si les DIMM sont peuplés différemment, si l’interleaving BIOS diffère, si le clustering NUMA est activé, ou si la mémoire d’un socket est dégradée.
Et oui : l’IMC a aussi rapproché certains problèmes de votre pager. Quand un canal mémoire devient instable, ce n’est pas juste « un DIMM ». Cela peut se manifester par des tempêtes d’erreurs corrigées, du throttling, ou un hôte qui semble correct jusqu’à être soumis à une charge réelle. Vous ne pouvez plus incriminer « le chipset » à l’infini. Le CPU est désormais le chipset, du moins pour la mémoire.
Faits intéressants et contexte historique
Voici des points concrets à garder dans votre modèle mental. Ce ne sont pas des anecdotes ; ce sont des leviers qui ont changé la conception et l’exploitation.
- AMD a popularisé tôt les IMC sur les serveurs x86-64 (ère Opteron), rendant le NUMA pratique avant que beaucoup d’équipes n’aient des outils matures.
- La génération Nehalem d’Intel a apporté les IMC à sa gamme serveur, et l’ère du front-side bus a pratiquement pris fin pour le scaling multiprocesseur sérieux.
- QPI/UPI et HyperTransport/Infinity Fabric d’AMD existent principalement pour transporter le trafic de cohérence de cache et l’accès mémoire distant entre sockets une fois que chaque socket possède sa mémoire.
- La mémoire multi-canal est devenue le réglage de performance par défaut : « plus de GHz » a cessé d’être aussi efficace que « plus de canaux correctement peuplés ».
- La conscience NUMA a remonté dans la pile : planificateurs du noyau, allocateurs, JVM, bases de données et hyperviseurs ont dû apprendre où vit la mémoire.
- La gestion des erreurs ECC s’est complexifiée : les IMC suivent les erreurs par canal/rang, et le firmware peut prendre des actions comme la retraite de pages ou la désactivation d’un canal.
- La fréquence mémoire est souvent réduite par la population : ajouter plus de DIMM par canal peut amener l’IMC à réduire la vitesse pour maintenir l’intégrité du signal.
- L’« interleaving » est devenu un choix stratégique : il peut lisser la bande passante, mais aussi effacer la localité, selon sa configuration.
- Les IMC intégrés + de nombreux cœurs ont fait de la « bande passante par cœur » une vraie limite : plus de cœurs signifie parfois moins de mémoire par cœur et plus de contention.
Latence, bande passante, et pourquoi la « vitesse CPU » ne suffit plus
Quand on dit « l’IMC a rendu la mémoire plus rapide », on comprime généralement deux histoires différentes : latence et bande passante. En production, les confondre conduit à régler le mauvais problème.
Latence : la taxe cachée sur chaque défaut de cache
Les caches CPU sont rapides mais petits. Votre charge de travail négocie avec le cache. Si votre working set tient, vous avez l’air héroïque. Sinon, vous payez la latence DRAM. Les IMC réduisent le coût de base d’accès à la DRAM comparé au design northbridge, mais ils introduisent aussi une réalité plus dure : sur les systèmes multi-socket, une partie de la DRAM est « proche » (locale) et une autre est « éloignée » (remote).
L’accès à la mémoire distante n’est pas seulement plus lent ; il est variable. Il dépend de la contention sur l’interconnect, du trafic de snoop, et de ce que fait le reste du système. Cette variabilité est un poison pour les latences en tail. Si vous poursuivez les p99, vous ne voulez pas seulement de la mémoire rapide. Vous voulez des accès mémoire consistants.
Bande passante : le tuyau est par socket, pas par rack
Un IMC expose typiquement plusieurs canaux mémoire. Si vous les peuplez correctement, vous avez de la bande passante. Si vous les peuplez paresseusement, vous obtenez un serveur qui semble « correct » sous charge légère puis s’effondre quand vous poussez.
Les problèmes de bande passante sont sournois parce que l’utilisation CPU peut être faible alors que les performances sont médiocres. Vous êtes bloqué, pas occupé. C’est pourquoi « le CPU est à 35 % » n’est pas rassurant ; c’est un indice.
Deux règles simples qui font gagner du temps
- Si votre latence est mauvaise, vous luttez probablement contre la localité ou des défauts de cache. Ajouter des threads aggrave souvent la situation.
- Si votre débit est mauvais, vous pourriez être limité par la bande passante. Ajouter des canaux mémoire ou corriger le placement peut rapporter plus que d’optimiser le code.
Une citation que je garde affichée, parce qu’elle est opérationnellement vraie : « La propriété la plus importante d’un programme est qu’il soit correct. »
— Donald Knuth. La performance vient après, mais la correction inclut un comportement prévisible sous charge, là où les réalités IMC/NUMA se manifestent.
NUMA : la fonctionnalité que vous utilisez par accident
NUMA (Non-Uniform Memory Access) est la conséquence naturelle des IMC dans les designs multi-socket. Chaque socket a son propre contrôleur mémoire et des DIMM attachés. Quand un cœur du socket 0 lit de la mémoire attachée au socket 1, il traverse un interconnect. C’est la mémoire distante.
En théorie, NUMA est simple : garder les threads proches de leur mémoire. En pratique, c’est comme les placements de tables à un mariage. Tout est calme jusqu’à ce que le cousin de quelqu’un arrive et refuse sa table.
Modes de défaillance NUMA que vous verrez réellement
- Pics de latence après montée en charge : vous ajoutez des workers, ils se répartissent sur les sockets, les allocations mémoire restent sur le nœud d’origine, et maintenant la moitié des accès est distante.
- Surprise en virtualisation : vous allouez une VM 64 vCPU avec 512 Go de RAM et l’hyperviseur la place sur plusieurs nœuds de façon à rendre l’accès distant la valeur par défaut.
- « Un hôte est plus lent » : même modèle CPU, mais population DIMM différente ou réglages BIOS NUMA différents. Félicitations, vous avez construit un cluster hétérogène sans le vouloir.
Joke courte #1 : NUMA signifie « Non-Uniform Memory Access », mais en production on le lit souvent comme « Now U Must Analyze ».
Interleaving : un couteau à double tranchant
L’interleaving répartit les adresses mémoire entre les canaux (et parfois entre les sockets) pour augmenter le parallélisme et lisser la bande passante. C’est bénéfique pour les charges gourmandes en bande passante et tolérantes à la latence. Cela peut être catastrophique pour les charges sensibles à la latence qui dépendent de la localité, parce que cela garantit du trafic distant.
Ne traitez pas « interleaving : activé » comme une vertu par défaut. Considérez-le comme une hypothèse à valider contre votre charge.
Stockage et IMC : pourquoi votre « goulot disque » est souvent la mémoire
Je vais agacer les spécialistes stockage (je le suis) : beaucoup de « problèmes de performance stockage » sont en réalité des problèmes mémoire avec un meilleur marketing. Les piles de stockage modernes sont gourmandes en mémoire : page cache, ARC, caches de métadonnées, planificateurs IO, chiffrement, compression, checksums, buffers de réplication et caches côté client dans l’application elle-même.
Avec les IMC, la bande passante mémoire et la localité déterminent la vitesse à laquelle votre système peut faire transiter les données par le CPU, pas seulement vers et depuis le disque. NVMe a rendu cela douloureusement évident : la latence stockage a suffisamment chuté pour que les stalls mémoire et les frais CPU côté application deviennent le goulot.
Où l’IMC/NUMA mord les systèmes lourds en stockage
- Les pipelines de checksum et compression deviennent des exercices de bande passante mémoire si vous scannez de gros blocs.
- Convergence réseau + stockage (ex. NVMe/TCP, iSCSI, piles RDMA) peut finir avec des queues RX/TX liées à des cœurs d’un socket tandis que les allocations mémoire viennent d’un autre.
- ZFS ARC et le page cache Linux peuvent devenir majoritairement distants si vos threads IO et allocations mémoire ne sont pas alignés.
Quand quelqu’un dit « Les disques sont lents », demandez : « Ou sommes-nous lents à nourrir les disques ? » Les IMC ont rendu cette question plus fréquente.
Méthode de diagnostic rapide
Voici l’ordre que j’utilise quand le business crie et que les graphiques mentent. L’objectif est de décider rapidement : CPU compute, latence mémoire, bande passante mémoire, ou IO/réseau. Ensuite on creuse.
Premier point : confirmer qu’il ne s’agit pas d’une saturation IO évidente
- Vérifier la file d’exécution et l’attente IO. Si le CPU est inactif mais que la moyenne de charge est élevée, vous êtes bloqué quelque part.
- Vérifier l’utilisation et l’encombrement des périphériques. Si un NVMe est saturé avec des files profondes, il s’agit probablement d’une vraie pression IO.
Deuxième point : identifier les stalls mémoire et problèmes de localité
- Vérifier la topologie NUMA et les allocations. Si la mémoire est majoritairement allouée sur le nœud 0 mais que les threads tournent sur les deux sockets, vous payez des pénalités distantes.
- Vérifier les compteurs de bande passante mémoire (les outils des fournisseurs aident, mais perf et outils Linux standards permettent déjà beaucoup).
Troisième point : vérifier la population DIMM et la fréquence
- Vérifier que tous les canaux sont peuplés comme prévu. Canaux manquants = bande passante manquante.
- Vérifier la vitesse mémoire réelle. L’étiquette du DIMM est aspirative ; l’IMC impose la réalité.
Quatrième point : agir avec le moindre impact
- Privilégier le pinning et les changements de politique (numactl, systemd CPUAffinity/NUMAPolicy) plutôt que les modifications BIOS durant un incident.
- Changer une seule chose, mesurer p95/p99, et revenir en arrière si la queue s’est dégradée.
Tâches pratiques : commandes, sorties, décisions (12+)
Ce sont des actions réalistes sur un serveur Linux typique. Il existe des outils spécifiques aux fournisseurs, mais vous pouvez diagnostiquer beaucoup avec des utilitaires de base. Chaque tâche inclut : commande, ce que signifie la sortie, et la décision à prendre.
Task 1: See NUMA topology quickly
cr0x@server:~$ lscpu | egrep -i 'Socket|NUMA|Core|Thread|Model name'
Model name: Intel(R) Xeon(R) Gold 6230 CPU @ 2.10GHz
Thread(s) per core: 2
Core(s) per socket: 20
Socket(s): 2
NUMA node(s): 2
NUMA node0 CPU(s): 0-19,40-59
NUMA node1 CPU(s): 20-39,60-79
Signification : Deux nœuds NUMA ; les IDs CPU mappent aux sockets/nœuds. Ce mapping est votre vocabulaire de pinning.
Décision : Si la charge est sensible à la latence, prévoyez de garder ses threads les plus chauds et la mémoire sur un seul nœud quand c’est possible (ou alignez des shards par nœud).
Task 2: Check how memory is distributed across NUMA nodes
cr0x@server:~$ numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
node 0 size: 192000 MB
node 0 free: 15000 MB
node 1 cpus: 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
node 1 size: 192000 MB
node 1 free: 160000 MB
node distances:
node 0 1
0: 10 21
1: 21 10
Signification : Le nœud 0 est presque plein tandis que le nœud 1 est majoritairement vide. C’est un signe classique d’allocation biaisée ou d’un process épinglé sur le nœud 0.
Décision : Si vos threads tournent sur les deux nœuds mais que la mémoire est biaisée, appliquez une politique NUMA (interleave ou bind) par service, ou corrigez l’affinité CPU pour faire correspondre l’allocation.
Task 3: Identify top processes causing remote memory traffic (NUMA hints)
cr0x@server:~$ numastat -p 12345
Per-node process memory usage (in MBs) for PID 12345 (myservice)
Node 0 Node 1 Total
--------------- --------------- ---------------
Huge 0.00 0.00 0.00
Heap 98000.50 1200.25 99200.75
Stack 50.10 48.90 99.00
Private 1020.00 900.00 1920.00
--------------- --------------- ---------------
Total 100070.60 2149.15 102219.75
Signification : Le heap du process est massivement sur le nœud 0. Si ses threads sont répartis sur les nœuds, des lectures distantes vont se produire.
Décision : Pour les services sensibles à la latence, épinglez les CPUs et la mémoire au nœud 0 (ou déplacez le process) plutôt que le laisser flotter.
Task 4: Verify actual memory speed and population
cr0x@server:~$ sudo dmidecode -t memory | egrep -i 'Locator:|Size:|Speed:|Configured Memory Speed:'
Locator: DIMM_A1
Size: 32 GB
Speed: 3200 MT/s
Configured Memory Speed: 2933 MT/s
Locator: DIMM_B1
Size: 32 GB
Speed: 3200 MT/s
Configured Memory Speed: 2933 MT/s
Signification : Des DIMM annoncés 3200 MT/s tournent à 2933 MT/s. Cela peut être normal selon la génération CPU et le nombre de DIMM par canal.
Décision : Si la bande passante est un problème, vérifiez les règles de population BIOS et si vous pouvez réduire les DIMM par canal ou utiliser des configurations supportées pour augmenter la fréquence.
Task 5: Confirm all memory channels are active (quick sanity)
cr0x@server:~$ sudo lshw -class memory | egrep -i 'bank:|size:|clock:'
bank:0
size: 32GiB
clock: 2933MHz
bank:1
size: 32GiB
clock: 2933MHz
Signification : On voit au moins plusieurs banks peuplées. Ce n’est pas une cartographie complète des canaux, mais cela détecte les erreurs de type « moitié des DIMM manquants ».
Décision : Si vous en attendiez plus et que vous ne les voyez pas, arrêtez de tuner le logiciel et ouvrez un ticket matériel.
Task 6: Detect memory pressure vs cache (is it really memory?)
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 376Gi 210Gi 12Gi 2.0Gi 154Gi 160Gi
Swap: 8Gi 0.0Gi 8Gi
Signification : « available » est sain. Cela suggère que vous n’êtes pas en thrashing purement dû à un manque de RAM ; les stalls peuvent être liés à la latence/bande passante/localité.
Décision : N’ajoutez pas de swap ou de RAM aveuglément. Investiguer la localité, la bande passante et les stalls CPU.
Task 7: Watch major faults and CPU steal (VM reality check)
cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 0 0 123456 10240 987654 0 0 0 8 3200 9000 35 10 55 0 0
4 0 0 122900 10240 987620 0 0 0 16 3300 9500 38 12 50 0 0
Signification : Pas de swap (si/so zéro), faible IO wait. Cela oriente loin du paging et vers des stalls compute/mémoire.
Décision : Passer aux compteurs perf et aux vérifications d’allocation NUMA.
Task 8: Check per-node memory allocation in the kernel (macro view)
cr0x@server:~$ cat /proc/zoneinfo | egrep -n 'Node 0, zone|Node 1, zone|present|managed'
14:Node 0, zone Normal
22: present 50331648
23: managed 49283072
420:Node 1, zone Normal
428: present 50331648
429: managed 49283072
Signification : Les deux nœuds ont des zones mémoire ; aucun nœud évident hors ligne. Cela aide à écarter une mauvaise configuration « le nœud manque ».
Décision : Si un nœud avait beaucoup moins de mémoire gérée, suspectez des réglages BIOS (mirroring, sparing) ou des problèmes matériels.
Task 9: See if automatic NUMA balancing is enabled (and decide if you trust it)
cr0x@server:~$ cat /proc/sys/kernel/numa_balancing
1
Signification : L’équilibrage NUMA du noyau est activé. Il peut aider les charges généralistes, et il peut aussi introduire surcharge ou jitter pour les services sensibles à la latence.
Décision : Pour les charges critiques en tail-latency, testez avec désactivation sur un hôte ou par service (des contrôles cgroup existent) ; ne basculez pas en production sur toute la flotte pendant les heures d’affluence.
Task 10: Find obvious CPU stalls (high-level perf view)
cr0x@server:~$ sudo perf stat -a -e cycles,instructions,cache-misses,stalled-cycles-frontend,stalled-cycles-backend -I 1000 sleep 5
# time counts unit events
1.000167156 3,201,234,567 cycles
1.000167156 1,120,456,789 instructions
1.000167156 34,567,890 cache-misses
1.000167156 1,050,123,456 stalled-cycles-frontend
1.000167156 1,980,234,567 stalled-cycles-backend
Signification : Les stalls backend sont énormes par rapport aux cycles. Cela indique souvent des limites de latence/bande passante mémoire, pas un manque de CPU.
Décision : Ne plus ajouter de threads. Investiguer la localité, l’adaptation des structures de données au cache et la saturation de la bande passante mémoire.
Task 11: Confirm IRQ and NIC queues aren’t pinning everything to one socket
cr0x@server:~$ cat /proc/interrupts | head -n 5
CPU0 CPU1 CPU2 CPU3
24: 1234567 0 0 0 IO-APIC 24-fasteoi eth0-TxRx-0
25: 0 9876543 0 0 IO-APIC 25-fasteoi eth0-TxRx-1
26: 0 0 7654321 0 IO-APIC 26-fasteoi eth0-TxRx-2
Signification : Les interruptions sont distribuées entre les CPU, du moins dans cet extrait. Si vous voyiez toutes les IRQ sur des CPU du nœud 0 seulement, attendez-vous à du trafic mémoire cross-node pour les applications réseau-intensives.
Décision : Si les IRQ sont biaisées, utilisez irqbalance ou réglez manuellement l’affinité pour aligner les queues NIC avec le socket qui exécute l’application.
Task 12: Inspect per-process CPU affinity and NUMA policy
cr0x@server:~$ taskset -pc 12345
pid 12345's current affinity list: 0-79
Signification : Le process peut tourner n’importe où. C’est acceptable pour des services throughput ; risqué pour des services sensibles à la latence qui allouent tôt puis migrent.
Décision : Envisagez d’épingler à un nœud (ou d’utiliser des cpusets) pour garder scheduling et allocation alignés.
Task 13: Pin a service to a NUMA node (controlled experiment)
cr0x@server:~$ sudo systemctl stop myservice
cr0x@server:~$ sudo numactl --cpunodebind=0 --membind=0 /usr/local/bin/myservice --config /etc/myservice/config.yaml
...service starts...
Signification : Vous avez forcé l’exécution et l’allocation mémoire sur le nœud 0. C’est un test pour vérifier si la mémoire distante vous pénalisait.
Décision : Si la latence p99 s’améliore et que le débit reste acceptable, rendez cela permanent via l’unité systemd (avec CPU/memory policy explicite) ou les réglages de l’orchestrateur.
Task 14: Compare “interleave memory” policy (when you want bandwidth smoothing)
cr0x@server:~$ sudo numactl --interleave=all /usr/local/bin/batch-job --input /data/scan.dat
...job runs...
Signification : Les allocations mémoire sont réparties sur les nœuds, ce qui peut augmenter la bande passante agrégée et réduire les points chauds.
Décision : Utilisez cela pour les charges batch/analytics qui privilégient le débit plutôt que la latence tail. Ne l’utilisez pas pour des services sensibles au p99 sans mesure préalable.
Task 15: Check ECC corrections (early warning for “mysterious slowness”)
cr0x@server:~$ sudo dmesg -T | egrep -i 'EDAC|ecc|corrected|uncorrected' | tail -n 5
[Mon Jan 8 10:42:12 2026] EDAC MC0: 1 CE memory read error on CPU_SrcID#0_Ha#0_Chan#1_DIMM#0 (channel:1 slot:0 page:0x12345 offset:0x0 grain:32 syndrome:0x0)
[Mon Jan 8 10:42:13 2026] EDAC MC0: 1 CE memory read error on CPU_SrcID#0_Ha#0_Chan#1_DIMM#0 (channel:1 slot:0 page:0x12346 offset:0x0 grain:32 syndrome:0x0)
Signification : Des erreurs corrigées surviennent sur un canal/DIMM spécifique. Le système est « OK », jusqu’à ce que ce ne soit plus le cas. Certaines plateformes vont aussi throttler ou retirer des pages.
Décision : Si les erreurs corrigées augmentent, planifiez le remplacement des DIMM. N’attendez pas une erreur non corrigée pour agir.
Task 16: Validate memory locality from a running process (quick view)
cr0x@server:~$ grep -E 'Cpus_allowed_list|Mems_allowed_list' /proc/12345/status
Cpus_allowed_list: 0-79
Mems_allowed_list: 0-1
Signification : Aucune restriction : le process peut allouer depuis n’importe quel nœud et s’exécuter sur n’importe quel CPU. La flexibilité n’est pas synonyme de performance.
Décision : Pour une latence stable et basse, resserrez cela via des cgroups cpuset ou des wrappers numactl pour réduire le churn inter-nœuds.
Trois mini-récits d’entreprise issus du terrain
1) Incident causé par une mauvaise hypothèse : « Toute la RAM est à la même distance »
L’équipe migrait une API sensible à la latence d’anciens serveurs mono-socket vers de jolis serveurs dual-socket. Même noyau, mêmes images de conteneur, même load balancer. Les nouveaux hôtes avaient plus de cœurs, plus de RAM, et la fiche technique qui fait croire à l’acheteur qu’il est aussi ingénieur perf.
En quelques heures, la latence p99 a augmenté. Pas un peu. Suffisamment pour que les retries s’empilent et que les services en amont amplifient le problème. L’utilisation CPU semblait normale, les disques étaient inactifs, et les graphiques réseau racontaient le mensonge habituel : « tout est vert ». On a discuté garbage collection et pools de threads parce que c’est ce que font les gens quand ils ne voient pas le goulot.
L’hypothèse erronée était simple : ils traitaient la mémoire comme un pool uniforme. L’application démarrait sur un socket, allouait la majeure partie de son heap localement, puis répartissait les workers sur les deux sockets. La moitié des workers consommaient donc de la mémoire distante. Le débit restait correct, la latence tail non.
La correction n’était pas exotique. Ils ont épinglé le process à un nœud NUMA et réduit légèrement le nombre de workers. La latence s’est stabilisée immédiatement. Plus tard, ils ont réarchitecturé le service pour sharder par nœud et exécuter deux instances par hôte, chacune liée localement. Le matériel n’avait pas changé ; le modèle mental, oui.
2) Optimisation qui a échoué : « Interleaver tout pour l’équité »
Une autre entreprise gérait un cluster à charges mixtes : batch analytics, services interactifs, et quelques systèmes stateful que tout le monde prétendait être stateless. Quelqu’un a activé un interleaving mémoire agressif dans le BIOS sur un ensemble d’hôtes parce que « ça améliore la bande passante » et « ça rend l’utilisation mémoire équitable ». Les deux sont vrais comme « rouler plus vite réduit le temps de trajet ».
Les jobs batch se sont améliorés. Les services interactifs sont devenus étranges. Pas systématiquement plus lents — plus erratiques. Le p50 bougeait à peine, mais le p99 s’est creusé des dents. Les ingénieurs ont traqué les spikes parmi les suspects habituels : pauses GC, voisins bruyants, upgrades noyau, un déploiement précis. Rien ne se reproduisait de façon fiable car le déclencheur était : « chaque fois que le service touchait de la mémoire qui devait maintenant rebondir entre sockets ».
Finalement, quelqu’un a comparé deux hôtes côte à côte et noté la différence BIOS. Ils ont rétabli l’interleaving sur la couche batch uniquement, et l’ont désactivé sur la couche latence. La leçon n’était pas « l’interleaving est mauvais ». La leçon était : arrêtez d’appliquer des optimisations de débit aux charges sensibles au tail-latency puis soyez surpris quand vos SLO se plaignent.
Ils ont aussi ajouté une vérification préliminaire lors du provisioning : enregistrer les réglages NUMA et d’interleaving dans les facts d’hôte, et refuser l’admission d’un hôte si cela ne correspond pas au rôle. Politique ennuyeuse, gros impact.
3) Pratique ennuyeuse mais correcte qui a sauvé la journée : « Population DIMM uniforme et validation hôte »
Celle-ci est peu glamour, ce qui explique pourquoi elle a fonctionné. Une équipe plateforme avait une règle : chaque classe de serveur doit avoir une population DIMM identique par canal, et chaque hôte doit passer un script de validation de topologie matérielle avant de rejoindre une piscine. Aucune exception « on réglera plus tard ».
Les achats ont essayé de substituer des DIMM lors d’une pénurie. Même capacité, rangs et vitesses différents. Ça démarrait, bien sûr. Mais cela abaissait aussi la fréquence mémoire sur ce socket et changeait l’enveloppe de performance. Le script de validation a signalé le mismatch : la vitesse mémoire configurée ne correspondait pas au profil attendu et un canal montrait un nombre anormal d’erreurs pendant le burn-in.
Cet hôte n’a jamais rejoint la couche base de données de production. Il a été redirigé vers une piscine dev où la variation de performance est une nuisance, pas un événement de revenu. Plus tard, un lot de DIMM a montré des erreurs corrigées élevées dans une autre région. Parce que l’équipe disposait de données de référence, ils ont pu corréler rapidement et remplacer proactivement les modules.
Pas de magie. Juste de la cohérence, des mesures, et la discipline de considérer la topologie matérielle comme une partie du contrat logiciel.
Erreurs fréquentes : symptômes → cause → correction
Si les IMC et NUMA ont un cadeau, c’est de rendre les mauvaises hypothèses coûteuses. Voici des motifs que je vois souvent, en langage que votre pager comprend.
1) Symptom: p99 latency gets worse after “bigger server” upgrade
- Cause racine : Les threads se répartissent sur les sockets ; les allocations mémoire sont restées locales à un socket ; l’accès distant est devenu courant.
- Correction : Exécuter une instance par nœud NUMA, ou épingler CPU/mémoire avec cpusets/numactl. Vérifier avec numastat et les compteurs de stalls perf.
2) Symptom: Throughput plateaus early; CPU utilization looks low
- Cause racine : Saturation de la bande passante mémoire ou stalls backend ; les cœurs attendent la mémoire.
- Correction : Réduire la concurrence, améliorer la localité des données, s’assurer que tous les canaux mémoire sont peuplés, et valider que la fréquence mémoire n’a pas été réduite.
3) Symptom: One host is consistently slower than its peers
- Cause racine : Mismatch de population DIMM, différents réglages BIOS interleaving/clustering, ou canal mémoire dégradé.
- Correction : Comparer dmidecode configured speed, topologie numactl, logs dmesg EDAC. Mettre l’hôte en quarantaine jusqu’à ce qu’il corresponde au profil.
4) Symptom: Spiky latency under network-heavy load
- Cause racine : IRQ NIC et threads workers sur un socket tandis que les buffers/allocations sont sur un autre ; pics de trafic mémoire cross-node.
- Correction : Aligner l’affinité IRQ et l’épinglage des process sur le même nœud NUMA ; s’assurer que les queues RSS sont correctement distribuées.
5) Symptom: “We added RAM and it got slower”
- Cause racine : L’ajout de DIMM a augmenté le nombre de DIMM par canal et forcé un downclock ; ou changé le mix de ranks affectant les timings.
- Correction : Re-vérifier la vitesse mémoire configurée ; suivre le guide de population plateforme ; préférer moins de DIMM à plus forte capacité par canal si la vitesse compte.
6) Symptom: Latency fine in staging, worse in production
- Cause racine : Le staging est mono-socket ou NUMA plus petit ; la production est multi-socket avec comportement mémoire distant.
- Correction : Tester sur une topologie représentative. Si impossible, imposer l’épinglage et limiter la taille des instances pour tenir dans un nœud.
7) Symptom: Random reboots or “MCE” events, preceded by subtle slowdowns
- Cause racine : Erreurs ECC qui s’aggravent ; retraite de pages ; instabilité d’un canal mémoire interagissant avec le comportement de l’IMC.
- Correction : Surveiller les logs EDAC/MCE et les taux d’erreur ; remplacer les DIMM de façon proactive ; ne pas considérer les erreurs corrigées comme « sans conséquence ».
Joke courte #2 : L’ECC, c’est comme la ceinture de sécurité — vous ne la remarquez que quand elle fait son travail, et ça gâche votre journée de toute façon.
Listes de contrôle / plan étape par étape
Checklist: bring a new server class into a latency-sensitive pool
- Topologie de référence : enregistrer les sorties
lscpuetnumactl --hardwarepar classe d’hôte. - Valider la population DIMM : s’assurer que tous les canaux sont peuplés comme prévu ; pas d’hôtes en « demi-configuration ».
- Confirmer la vitesse mémoire configurée : capturer
dmidecode -t memoryet comparer avec l’attendu. - Vérifier la cohérence des politiques BIOS : interleaving, clustering NUMA, mode power/performance — maintenir l’uniformité pour la piscine.
- Burn-in avec compteurs : lancer une charge qui stresse la bande passante mémoire et surveiller les erreurs ECC corrigées.
- Enregistrer les facts hôte : stocker la topologie et les signatures BIOS ; bloquer l’admission en cas de mismatch.
Checklist: incident response when latency spikes
- Confirmer l’étendue : un seul hôte, une AZ, ou la flotte entière ?
- Vérifier le skew NUMA :
numactl --hardware,numastat -ppour le coupable principal. - Vérifier les stalls :
perf statpour stalls backend et cache misses. - Vérifier les logs ECC : sortie EDAC de
dmesgpour tempêtes d’erreurs corrigées. - Atténuer en sécurité : épingler le service à un nœud, réduire la concurrence, ou basculer le traffic hors de l’hôte.
- Seulement ensuite toucher le BIOS : planifier les changements ; n’expérimentez pas en live sauf si vous aimez rédiger des postmortems.
Step-by-step plan: tune a service for NUMA without breaking it
- Mesurer la base : capturer p50/p95/p99, débit, utilisation CPU et usage mémoire.
- Mapper les threads aux CPUs : identifier où les threads tournent et où la mémoire s’alloue (taskset + numastat).
- Choisir une stratégie :
- Priorité latence : exécuter une instance par nœud NUMA, binder mémoire et CPUs localement.
- Priorité débit : interleaver la mémoire, distribuer les IRQ, accepter un certain trafic distant.
- Appliquer minimalement : commencer par un wrapper
numactlou l’affinité systemd ; éviter les changements BIOS en premier lieu. - Re-mesurer : surtout p99 et la gigue. Si le p50 s’améliore mais que le p99 se dégrade, vous n’avez pas gagné.
- Verrouiller la politique : l’encoder dans le déploiement (unité systemd, CPU manager Kubernetes, config hyperviseur).
- Garde-fou : alerter sur le déséquilibre NUMA et les corrections ECC ; mettre en quarantaine les hôtes qui dérivent.
FAQ
1) Is an integrated memory controller always faster?
Une latence plus faible et une bande passante plus élevée sont typiques, oui. Mais « plus rapide » devient conditionnel : la mémoire locale est rapide ; la mémoire distante est plus lente ; la configuration détermine si vous en profitez.
2) Why did my p99 get worse after moving from 1-socket to 2-socket?
NUMA. Votre charge utilise probablement de la mémoire distante. La correction : garder threads et mémoire locaux (pinning, sharding, ou une instance par nœud).
3) Should I enable memory interleaving?
Pour du batch orienté débit, souvent oui. Pour des services sensibles à la latence, cela peut augmenter les accès distants et la gigue. Mesurez avec votre charge ; ne vous fiez pas au folklore.
4) How do I know if I’m bandwidth-bound or latency-bound?
Limitation par bande passante montre souvent un plateau de débit avec une utilisation CPU plutôt basse et de forts stalls backend ; limitation par latence montre une sensibilité p99 aux accès distants et aux cache misses. Utilisez les compteurs perf de stalls et les vérifications d’allocation NUMA.
5) Why does adding DIMMs sometimes reduce memory speed?
Plus de DIMM par canal augmente la charge électrique. L’IMC peut réduire la fréquence pour maintenir la stabilité. Cela peut réduire la bande passante et augmenter légèrement la latence selon la génération et les timings.
6) Is kernel automatic NUMA balancing enough?
Il aide les charges génériques, mais ce n’est pas un substitut au placement intentionnel pour les systèmes critiques en latence. Il peut aussi ajouter overhead et imprévisibilité. Traitez-le comme un outil, pas une garantie.
7) How does this relate to virtualization?
Les VM peuvent couvrir plusieurs nœuds NUMA. Si le placement vCPU et la mémoire ne sont pas alignés, la mémoire distante devient la valeur par défaut. Taillez les VM pour qu’elles tiennent dans un nœud quand c’est possible, ou utilisez des règles de placement NUMA-aware.
8) What’s the most practical thing to do first for a database?
Gardez-la locale. Exécutez une instance unique liée à un nœud (si elle tient), ou exécutez plusieurs instances/shards par nœud. Puis validez avec numastat et les métriques de latence.
9) Do IMCs change reliability, not just performance?
Ils changent l’observabilité et le traitement. ECC, retraite de pages, et dégradation de canaux se manifestent via le reporting de l’IMC. Vous devez surveiller les erreurs corrigées et prendre les tendances au sérieux.
10) What about single-socket systems—do I care?
Oui, mais différemment. Vous vous souciez toujours des canaux mémoire, de la population DIMM et de la vitesse configurée. La complexité NUMA est moindre, mais les effets de bande passante et de downclock persistent.
Prochaines étapes pratiques
Si vous exploitez des systèmes de production, les actions ne sont pas philosophiques :
- Standardiser la topologie matérielle par piscine : même population DIMM, mêmes politiques BIOS mémoire, mêmes réglages NUMA.
- Rendre NUMA visible : tableaux de bord pour l’utilisation mémoire par nœud, indicateurs d’accès local vs distant (même approximatifs), et signaux de stalls backend.
- Choisir une stratégie de placement explicite par charge :
- Couche latence : binder et sharder par nœud, limiter la taille des instances, éviter l’interleaving « utile ».
- Couche batch : interleaver quand ça aide, viser le débit, accepter la variabilité.
- Opérationnaliser l’ECC : alerter sur les taux d’erreurs corrigées, mettre en quarantaine les hôtes en pic, remplacer les DIMM avant que l’IMC n’aggrave la situation.
- Tester sur une topologie représentative : un staging mono-socket est un joli mensonge. Il continuera à mentir jusqu’à ce que la production vous instruise.
Le passage à l’IMC n’était pas qu’un changement d’architecture. C’était un changement de contrat entre le logiciel et le matériel. Si vous traitez encore la mémoire comme un pool plat et uniforme, vous appliquez le modèle mental d’hier sur les machines d’aujourd’hui — et les machines vous feront payer des intérêts.