Vous avez acheté le CPU « plus rapide ». Fréquences supérieures, microarchitecture plus récente, une fiche technique pleine de promesses.
Et la latence en production s’est détériorée.
Ce n’est pas de la malchance. C’est de la physique, plus une pile d’hypothèses optimistes sur ce que signifie « plus rapide ».
Dans les systèmes réels, le CPU n’attend généralement pas lui-même. Il attend des données. Quand les données ne sont pas en cache,
le CPU devient un radiateur très coûteux qui fait parfois du travail.
Ce que « monstre du cache » signifie réellement
« Monstre du cache » n’est pas une insulte. C’est un compliment pour un système (ou une puce) qui gagne en gardant l’ensemble de travail
proche des cœurs. Le monstre peut être :
- Un CPU avec un grand cache de dernier niveau (LLC/L3), une haute bande passante et un bon préfetcher.
- Une plateforme avec des canaux mémoire rapides et une topologie NUMA cohérente.
- Une application qui se comporte comme si elle respectait la localité (et qui ne jette pas des pointeurs comme des confettis).
- Une pile de stockage avec une mise en cache agressive (page cache, buffer cache de base de données, ZFS ARC, Redis, CDN).
L’astuce est que « CPU plus rapide » signifie souvent « peut terminer plus d’instructions par seconde si les données sont déjà là ».
Si le système manque souvent le cache, vous n’êtes pas limité par le calcul maximal. Vous êtes limité par la vitesse de récupération.
Et récupérer, sur une machine moderne, est un processus bureaucratique à plusieurs étapes.
Pensez-y comme une cuisine. Votre chef peut couper plus vite (IPC plus élevé, fréquence plus haute), certes. Mais si les ingrédients
sont toujours sur un camion quelque part (miss de cache), la cuisine est très silencieuse.
La hiérarchie mémoire : où le temps va mourir
La latence est la vraie monnaie
Les ingénieurs systèmes l’apprennent à la dure : le débit fait la une, la latence écrit vos rapports d’incident.
Un seul miss de cache peut bloquer un cœur suffisamment longtemps pour rendre votre CPU « plus rapide » irrelevant.
Ce n’est pas théorique ; c’est pourquoi l’ingénierie des performances est principalement l’étude de l’attente.
Grossièrement, vous avez une échelle du « près » au « loin » :
- Registres : quasiment instantané.
- Cache L1 : minuscule et extrêmement rapide.
- Cache L2 : plus grand, toujours rapide.
- L3/LLC : partagé, plus grand, plus lent.
- DRAM : beaucoup plus lent, et partagé entre de nombreux cœurs affamés.
- Mémoire d’un nœud NUMA distant : encore plus lente.
- SSD/NVMe : des ordres de grandeur plus lents que la DRAM.
- Stockage réseau : là, vous négociez avec les lois de la géographie.
Le travail du CPU est d’exécuter des instructions. Votre travail est de fournir ces instructions en données provenant de l’endroit
le plus proche possible sur cette échelle. Car à chaque marche descendue, vous payez en cycles bloqués, en pannes de latence en queue,
et en graphiques de « utilisation CPU » suspects qui semblent normaux jusqu’à ce que les utilisateurs commencent à hurler.
Les caches ne sont pas juste de la « RAM petite »
Les caches sont construits autour de la localité :
- Localité temporelle : si vous l’avez utilisé, vous pourriez l’utiliser à nouveau bientôt.
- Localité spatiale : si vous avez utilisé cette adresse, vous pourriez utiliser des adresses voisines bientôt.
Les caches matériels opèrent sur des lignes de cache (généralement 64 octets). Cela signifie que vous ne récupérez pas un seul entier ; vous récupérez
aussi ses voisins, que cela vous plaise ou non.
C’est pourquoi « CPU plus rapide » est souvent battu par une « meilleure disposition ». Un CPU un peu plus lent qui obtient des hits de cache peut
battre un plus rapide qui détruit le LLC et passe sa vie sur des allers-retours DRAM.
La mathématique cruelle des misses
Un CPU peut terminer plusieurs instructions par cycle. L’exécution out-of-order moderne cache une partie de la latence, mais seulement jusqu’au
point où le cœur trouve d’autres travaux indépendants. Quand votre charge est faite de parcours de pointeurs, de branches ou de sérielisation
(commun dans les bases de données, allocateurs et de nombreux runtimes de haut niveau), le CPU manque rapidement de travail indépendant.
Alors vous vous bloquez. Fort.
C’est aussi pourquoi la « latence moyenne » peut mentir. Un miss de cache n’ajoute pas juste un peu de temps ; il peut transformer une requête
en un cas extrême de longue traîne. Les utilisateurs ne créent pas de tickets pour le p50. Ils réagissent quand « ça a gelé puis s’est rafraîchi ».
Pourquoi le CPU « plus rapide » perd en production
1) Votre charge n’est pas liée au calcul
Beaucoup de charges en production sont liées aux données :
- Bases de données lisant des index et suivant des pointeurs dans des B-arbres.
- Recherches clé/valeur avec des accès aléatoires.
- Microservices effectuant du parsing JSON, des allocations, des recherches dans des tables de hachage et du logging.
- Pipelines d’observabilité qui compressent, regroupent et envoient des événements avec des métadonnées lourdes.
Le CPU n’est pas « occupé » à faire de l’arithmétique. Il attend la mémoire, des verrous, la fin d’un I/O, la propriété d’une ligne de cache,
que l’allocateur trouve de l’espace libre, que le garbage collector arrête le monde et nettoie.
2) Les CPU « plus rapides » échangent souvent cache contre cœurs ou fréquence
Les SKU produits font cela en permanence. Vous verrez des CPU avec :
- Plus de cœurs mais moins de cache par cœur.
- Des fréquences boost plus élevées mais des limites de puissance serrées (donc sous charge soutenue la fréquence baisse).
- Différentes topologies de cache (slices LLC partagées, comportement d’interconnexion différent).
Si votre appli a besoin du cache, alors un CPU avec moins de cœurs mais plus de LLC peut surperformer un CPU « plus gros » qui force votre
ensemble de travail vers la DRAM. Voilà le monstre du cache : pas glamour, juste efficace.
3) NUMA et topologie sont des multiplicateurs (et des destructeurs) de performances
NUMA n’est pas une note académique. C’est la raison pour laquelle votre nouveau serveur est « mystérieusement plus lent » que l’ancien.
Si les threads tournent sur un socket mais allouent la mémoire sur un autre, vous payez une pénalité d’accès distant à presque chaque miss.
Maintenant votre cœur CPU « plus rapide » aspire des données à travers une paille plus longue.
La topologie affecte aussi la contention du cache partagé. Placez trop de voisins bruyants dans le même domaine LLC et votre taux de hit s’effondre.
Le CPU est rapide. Il a juste faim.
4) Le stockage est le miss de cache ultime
Quand vous manquez la mémoire et tombez sur le stockage, vous ne comparez plus les fréquences. Vous comparez des microsecondes et des millisecondes.
C’est là que les monstres du cache montrent leurs crocs : page cache, pools de buffers, ZFS ARC, caches d’objets, bords CDN.
Un hit de cache peut transformer « besoin d’une lecture disque » en « retour immédiat ». Ce n’est pas 10% d’accélération. C’est la survie.
5) La latence en queue punit les impatients
Les CPU plus rapides aident le p50 quand vous êtes lié au calcul. La mise en cache aide le p99 quand vous êtes lié aux données.
La production est majoritairement un business p99. Votre rotation d’astreinte certainement aussi.
Juste une citation, parce que les ingénieurs méritent au moins une bonne :
Tous ont un environnement de test. Certains ont la chance d’avoir un environnement de production séparé.
— Aphorisme anonyme des opérations
Blague #1 : Acheter un CPU plus rapide pour corriger des misses de cache, c’est comme acheter une voiture plus rapide parce que vous oubliez où vous l’avez garée.
Faits intéressants et contexte historique (ce qui explique la douleur d’aujourd’hui)
-
Le « mur de la mémoire » est devenu un problème nommé au milieu des années 1990 : les vitesses CPU ont progressé plus vite que la latence DRAM,
forçant les architectes à s’appuyer fortement sur les caches et le préfetching. -
Les premiers CPU n’avaient pas de cache sur puce : les caches sont passés on-die à mesure que le budget transistor augmentait, car la latence
du cache hors puce était trop coûteuse. -
Les lignes de cache existent parce que les transferts mémoire sont en blocs : récupérer 64 octets amortit le surcoût du bus, mais
cela signifie aussi que vous pouvez gaspiller de la bande passante en traînant des voisins inutiles. -
L’associativité est un compromis : une associativité plus élevée réduit les conflits de miss mais coûte en puissance et complexité.
Les puces réelles font des compromis pragmatiques qui se manifestent sous forme de « falaises » de performance mystérieuses. -
Les politiques LLC inclusives vs non-inclusives comptent : certains designs conservent les lignes de cache des niveaux supérieurs dupliquées dans le LLC,
ce qui affecte le comportement d’éviction et la capacité effective du cache. -
Les CPU modernes utilisent des préfetchers sophistiqués : ils devinent les accès mémoire futurs. Quand ils ont raison, vous avez l’air d’un génie. Quand ils ont tort,
ils peuvent polluer les caches et consommer la bande passante. -
NUMA est devenu courant avec la montée en puissance des serveurs multi-socket : l’accès mémoire local est plus rapide que le distant. L’ignorer est le moyen le plus rapide
de transformer « plus de sockets » en « plus de tristesse ». - Dans Linux, le page cache est une fonctionnalité de performance de première classe : c’est pourquoi une seconde exécution d’un job peut être nettement plus rapide que la première—à moins que vous ne le contourniez avec Direct I/O.
-
Le stockage est passé des HDD aux SSD puis NVMe : la latence a fortement baissé, mais elle reste bien inférieure à la DRAM.
Un hit de cache reste un univers à part des lectures de stockage.
Modes de défaillance : comment le cache et l’I/O vous mordent
Miss de cache qui ressemblent à des « problèmes CPU »
Le piège classique est de voir une forte utilisation CPU et de supposer qu’il faut plus de CPU. Parfois oui. Souvent non.
Une forte CPU peut signifier « exécution active d’instructions » ou « spin, bloqué, attente mémoire ».
Les deux apparaissent comme « CPU occupé » aux yeux non entraînés.
Les misses de cache se manifestent par :
- Un nombre d’instructions par cycle (IPC) bas malgré une forte utilisation CPU.
- Taux de miss LLC plus élevés sous charge.
- Débit qui cesse d’évoluer avec l’ajout de cœurs.
- Pics de latence quand l’ensemble de travail franchit une frontière de cache.
Faux partage et ping-pong de lignes de cache
Vous pouvez avoir beaucoup de cache et quand même perdre parce que les cœurs se battent pour les lignes de cache. Quand deux threads sur des cœurs différents
écrivent des variables différentes qui partagent la même ligne de cache, la ligne rebondit entre cœurs pour maintenir la cohérence.
Félicitations : vous avez inventé un système distribué à l’intérieur de votre CPU.
Miss TLB : le miss de cache furtif
Les TLB (Translation Lookaside Buffers) mettent en cache les traductions virtuel→physique. Quand vous manquez là, vous payez un surcoût de page-walk
avant même de récupérer les données. Les charges avec d’immenses espaces d’adresses, des accès aléatoires ou des tas fragmentés
peuvent transformer la pression TLB en latence.
Miss de cache de stockage : page cache, buffer pools et amplification de lecture
Les misses de page cache deviennent des lectures disque. Les lectures disque deviennent de la latence. Et si votre couche de stockage fait de l’amplification de lecture
(commun dans certains systèmes de fichiers copy-on-write, couches chiffrées, ou I/O mal alignées), vous pouvez « manquer » plus d’une fois par requête.
Beaucoup de « mises à niveau CPU » échouent parce que le chemin de stockage est le goulet d’étranglement, et le CPU passe juste plus de temps à attendre plus vite.
Blague #2 : Le moyen le plus rapide de rendre un système plus lent est de l’optimiser jusqu’à ce qu’il commence à faire du « travail utile » que vous n’avez pas demandé.
Guide rapide de diagnostic
Quand les performances régressent après une mise à niveau CPU (ou toute « amélioration matérielle »), ne devinez pas. Travaillez la pile de haut en bas
et forcez le système à avouer où il attend.
Premier : décidez si vous êtes lié au calcul, à la mémoire ou à l’I/O
- Lié au calcul : IPC élevé, CPU utilisateur élevé, peu de cycles bloqués, le scaling s’améliore avec plus de cœurs.
- Lié à la mémoire : IPC bas, nombreux misses cache/TLB, cycles bloqués élevés, l’échelle plafonne tôt.
- Lié à l’I/O : iowait présent, latence de stockage élevée, threads bloqués sur des lectures, pics de misses de page cache.
Second : vérifiez le comportement du cache et l’IPC sous charge réelle
- Utilisez
perfpour regarder cycles, instructions, misses de cache, cycles bloqués. - Surveillez les misses LLC et les compteurs de bande passante mémoire si disponibles.
- Recherchez des chutes soudaines quand le QPS augmente (l’ensemble de travail dépasse le cache).
Troisième : vérifiez la localité NUMA et le comportement de fréquence CPU
- Confirmez que la charge est épinglée de manière sensée ou au moins qu’elle ne se bat pas contre l’ordonnanceur.
- Vérifiez si les allocations mémoires sont locales au CPU qui exécute le thread.
- Vérifiez que vous n’êtes pas en throttling thermique/puissance sous charge soutenue.
Quatrième : suivez le miss de cache jusqu’à la pile de stockage
- Vérifiez les signaux de hit/miss du page cache (readahead, fautes majeures).
- Mesurez la distribution de latence du stockage, pas seulement le débit.
- Confirmez la profondeur de file d’attente, le planificateur et le comportement du système de fichiers (surtout les couches CoW/RAID).
Cinquième : seulement alors envisagez un changement de CPU
Si vous êtes lié au calcul, certes : achetez du CPU. Si vous êtes lié à la mémoire ou à l’I/O, corrigez la localité, la mise en cache et la disposition d’abord.
C’est moins cher et généralement plus efficace.
Tâches pratiques : commandes, sorties, ce qu’elles signifient et la décision que vous prenez
Ce sont des vérifications de qualité production. Exécutez-les pendant un test de charge ou une fenêtre d’incident réelle (avec précaution). Chaque tâche inclut :
la commande, une sortie représentative, ce que cela signifie, et ce que vous faites ensuite.
Task 1: Check CPU frequency and throttling behavior
cr0x@server:~$ lscpu | egrep 'Model name|CPU\(s\)|Socket|Thread|NUMA|MHz'
Model name: Intel(R) Xeon(R) Gold 6338 CPU @ 2.00GHz
CPU(s): 64
Thread(s) per core: 2
Socket(s): 2
NUMA node(s): 2
CPU MHz: 1199.842
Ce que cela signifie : Si MHz est bien en dessous de la base/attendu sous charge, vous pouvez être limité par la puissance/thermique ou par un governor conservateur.
Décision : Vérifiez le governor et la fréquence soutenue sous charge ; ne supposez pas que les fréquences boost sont réelles en production.
Task 2: Confirm CPU governor (performance vs powersave)
cr0x@server:~$ cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
powersave
Ce que cela signifie : « powersave » peut plafonner la fréquence et augmenter la latence sous charges en rafales.
Décision : Si ce nœud est sensible à la latence, passez à « performance » (ou ajustez la politique plateforme) pendant les tests et comparez.
Task 3: Quick view of run queue pressure and iowait
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
8 0 0 242112 81240 912340 0 0 0 12 6200 9800 62 10 22 6 0
10 1 0 240980 81240 910120 0 0 64 980 6900 11200 55 11 18 16 0
11 2 0 239500 81240 908400 0 0 128 1400 7200 11800 48 12 16 24 0
Ce que cela signifie : Un r élevé suggère une contention CPU ; un wa et b élevés suggèrent des attentes I/O. Cet exemple montre une iowait croissante.
Décision : Si l’iowait augmente avec la charge, ne perdez pas de temps sur des upgrades CPU—suivez le chemin I/O.
Task 4: Identify blocked tasks (often I/O or lock contention)
cr0x@server:~$ ps -eo state,pid,comm,wchan:32 | awk '$1 ~ /D/ {print}'
D 18423 postgres io_schedule
D 19011 java futex_wait_queue_me
Ce que cela signifie : L’état D indique un sommeil non interruptible, communément des attentes I/O (io_schedule) ou parfois des chemins de verrouillage du noyau.
Décision : Si de nombreux threads sont en D, la latence de stockage ou la contention du système de fichiers est un suspect principal.
Task 5: Measure cache misses and IPC with perf stat (quick, high value)
cr0x@server:~$ sudo perf stat -a -e cycles,instructions,cache-references,cache-misses,branches,branch-misses -I 1000 -- sleep 3
# time counts unit events
1.000353504 12,804,112,210 cycles
1.000353504 6,210,554,991 instructions # 0.49 insn per cycle
1.000353504 401,220,118 cache-references
1.000353504 121,884,332 cache-misses # 30.37% of all cache refs
1.000353504 1,002,884,910 branches
1.000353504 21,880,441 branch-misses # 2.18% of all branches
Ce que cela signifie : IPC ~0.49 et un ratio de cache-miss élevé crient des stalls mémoire. Un CPU « plus rapide » ne résoudra pas cela sauf s’il a un comportement cache/mémoire nettement meilleur.
Décision : Réduisez l’ensemble de travail, améliorez la localité, épinglez threads/mémoire pour NUMA, ou changez les structures de données avant d’acheter du calcul.
Task 6: Check NUMA placement and remote memory usage (if numactl is available)
cr0x@server:~$ numastat -p 18423
Per-node process memory usage (in MBs) for PID 18423 (postgres)
Node 0 8123.45
Node 1 421.12
Total 8544.57
Ce que cela signifie : La mémoire est majoritairement sur le Nœud 0. Si les threads tournent sur le Nœud 1, vous paierez des pénalités d’accès distant.
Décision : Alignez l’affinité CPU et la politique mémoire : épinglez les processus ou corrigez l’orchestration pour que l’allocation et l’exécution cohabitent.
Task 7: See where threads are running (CPU affinity and migration clues)
cr0x@server:~$ ps -eLo pid,tid,psr,comm | awk '$4=="postgres"{print $0}' | head
18423 18423 12 postgres
18423 18424 3 postgres
18423 18425 47 postgres
18423 18426 19 postgres
Ce que cela signifie : Des threads dispersés sur les CPUs peuvent être acceptables, ou bien hostiles au cache si la charge partage des structures chaudes et fait rebondir les lignes de cache.
Décision : Si vous observez de la contention et un mauvais scaling, testez l’épinglage sur un sous-ensemble de cœurs ou un seul nœud NUMA et mesurez le p99.
Task 8: Check major page faults (page cache misses that hit storage)
cr0x@server:~$ pidstat -r -p 19011 1 3
Linux 6.2.0 (server) 01/10/2026 _x86_64_ (64 CPU)
12:00:01 UID PID minflt/s majflt/s VSZ RSS %MEM Command
12:00:02 1001 19011 1200.00 85.00 12488192 2380040 7.42 java
12:00:03 1001 19011 1180.00 92.00 12488192 2381208 7.42 java
Ce que cela signifie : majflt/s indique des fautes majeures nécessitant un I/O disque (sauf si déjà en cache). C’est souvent un tueur de latence.
Décision : Augmentez la mémoire, réduisez le churn de fichiers, ou assurez-vous que les fichiers chauds restent en cache ; envisagez une mise en cache au niveau applicatif.
Task 9: Measure storage device latency and queueing
cr0x@server:~$ iostat -x 1 3
Linux 6.2.0 (server) 01/10/2026 _x86_64_ (64 CPU)
Device r/s w/s r_await w_await aqu-sz %util
nvme0n1 820.0 120.0 6.10 8.40 5.40 98.0
Ce que cela signifie : r_await/w_await sont des latences moyennes ; aqu-sz et un %util élevé indiquent de la mise en file et de la saturation.
Décision : Si le dispositif est saturé, réduisez l’I/O (mettez plus en cache), répartissez la charge sur plusieurs dispositifs, ou changez le pattern d’accès avant de toucher au CPU.
Task 10: Identify hot files and I/O sources (quick and dirty)
cr0x@server:~$ sudo lsof -nP | awk '{print $1,$2,$4,$9}' | egrep 'postgres|java' | head
postgres 18423 mem /usr/lib/x86_64-linux-gnu/libssl.so.3
postgres 18423 12u /var/lib/postgresql/15/main/base/16384/2619
java 19011 45u /var/log/app/service.log
Ce que cela signifie : Montre quels fichiers sont ouverts. Si vous voyez des fichiers de log ou des chemins inattendus dominer, votre « problème CPU » peut être du « logging à mort ».
Décision : Réduisez le logging synchrone, regroupez les écritures, déplacez les logs sur un volume plus rapide, ou corrigez le niveau de log.
Task 11: Check filesystem and mount options (Direct I/O, barriers, atime)
cr0x@server:~$ findmnt -no TARGET,SOURCE,FSTYPE,OPTIONS /var/lib/postgresql
/var/lib/postgresql /dev/md0 ext4 rw,relatime,data=ordered
Ce que cela signifie : Des options comme relatime comptent ; d’autres (modes de journal, barrières) affectent la latence et la sécurité.
Décision : Ne tunez qu’avec une raison claire. Si vous changez des options liées à la sécurité pour courir après des benchmarks, rédigez le post-mortem à l’avance.
Task 12: Observe page cache pressure and reclaim behavior
cr0x@server:~$ grep -E 'pgscan|pgsteal|pgfault|pgmajfault' /proc/vmstat | head -n 8
pgfault 1283394021
pgmajfault 228103
pgscan_kswapd 901223
pgscan_direct 188004
pgsteal_kswapd 720111
pgsteal_direct 141220
Ce que cela signifie : Une augmentation de pgscan_direct suggère un reclaim direct (processus qui récupèrent la mémoire eux-mêmes), ce qui peut nuire à la latence.
Décision : Si le reclaim direct est élevé pendant la charge, réduisez la pression mémoire : ajoutez de la RAM, réduisez le churn de cache, ajustez les limites mémoire, ou corrigez des heaps surdimensionnés.
Task 13: Check swap activity (a slow-motion disaster)
cr0x@server:~$ swapon --show
NAME TYPE SIZE USED PRIO
/dev/sda3 partition 16G 2G -2
Ce que cela signifie : L’utilisation du swap n’est pas automatiquement mauvaise, mais un swapping soutenu sous charge est une usine à misses de cache avec des étapes supplémentaires.
Décision : Si la latence compte, évitez le thrashing de swap : limitez la mémoire, corrigez les fuites, redimensionnez les heaps, ou ajoutez de la RAM.
Task 14: Check memory bandwidth pressure (top-level hint via perf)
cr0x@server:~$ sudo perf stat -a -e stalled-cycles-frontend,stalled-cycles-backend,LLC-loads,LLC-load-misses -I 1000 -- sleep 3
# time counts unit events
1.000339812 3,110,224,112 stalled-cycles-frontend
1.000339812 8,901,884,900 stalled-cycles-backend
1.000339812 220,112,003 LLC-loads
1.000339812 88,440,120 LLC-load-misses # 40.17% of all LLC loads
Ce que cela signifie : Les stalls backend et un taux élevé de misses LLC suggèrent fortement des limites de latence/bande passante mémoire.
Décision : Vous avez besoin d’améliorations de localité, pas de GHz bruts. Envisagez des changements de structures de données, du sharding, ou la co-localisation des données chaudes.
Task 15: Check ZFS ARC effectiveness (if on ZFS)
cr0x@server:~$ arcstat 1 3
time read miss miss% dmis dm% pmis pm% mmis mm% arcsz c
12:00:01 12K 2.8K 23 920 33 1.7K 61 180 6 96G 112G
12:00:02 13K 4.9K 37 2.1K 43 2.6K 53 200 4 96G 112G
Ce que cela signifie : Une hausse du pourcentage de miss ARC sous charge implique que votre ensemble de travail ne tient pas dans l’ARC ; les lectures tombent sur le disque.
Décision : Ajoutez de la RAM, ajustez les limites ARC, ajoutez un cache secondaire plus rapide si pertinent, ou réduisez l’ensemble de travail (bloat d’index, données froides).
Task 16: Validate network latency as “remote cache miss” (service calls)
cr0x@server:~$ ss -tin dst 10.20.0.15:5432 | head -n 12
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 0 10.20.0.10:44912 10.20.0.15:5432
cubic wscale:7,7 rto:204 rtt:1.8/0.4 ato:40 mss:1448 cwnd:10 bytes_acked:1234567 bytes_received:2345678
Ce que cela signifie : Le RTT vous indique si votre « requête lente » est en réalité de la « gigue réseau ». Dans les systèmes distribués, le réseau est juste un autre niveau de cache—un niveau coûteux.
Décision : Si RTT/gigue est élevé, focalisez-vous sur la co-localisation, le pooling de connexions, ou la mise en cache des résultats plus près de l’appelant.
Trois mini-récits d’entreprise depuis le terrain
Mini-récit 1 : L’incident causé par une mauvaise hypothèse (la « mise à niveau CPU » qui a empiré le p99)
Une entreprise de taille moyenne exploitait une API de recherche gourmande. L’équipe avait une histoire propre : les requêtes sont lentes, la CPU est élevée, achetez des CPUs plus rapides.
Les achats ont livré des serveurs plus récents avec des fréquences plus élevées et plus de cœurs. Les benchmarks sur une seule machine semblaient décents.
Le déploiement a commencé. En quelques heures, la latence p99 a explosé et les budgets d’erreur ont commencé à fondre.
L’hypothèse erronée était subtile : ils supposaient que « plus de CPU » améliore une charge déjà limitée par la localité mémoire.
Les nouveaux serveurs avaient plus de cœurs par socket, mais moins de LLC par cœur et des caractéristiques NUMA légèrement différentes.
Sous une vraie charge multi-tenant, l’ensemble de travail de la requête ne tenait plus bien dans le cache. Le taux de miss LLC a augmenté.
L’IPC a chuté. Les graphiques CPU semblaient toujours « occupés », ce qui a induit tout le monde en erreur plus longtemps qu’il n’aurait fallu.
La réponse à l’incident a pris une tournure gênante quand les ingénieurs ont réalisé que leur nouvelle flotte « plus rapide » était devenue meilleure pour
exécuter des stalls. Ils ont reverti le trafic, puis reproduit la régression dans un test contrôlé avec des compteurs perf.
La preuve accablante était une augmentation consistante des LLC load misses et des stalled cycles backend sous le même QPS.
La correction n’a pas été d’annuler l’achat matériel (ce navire était parti), mais de changer le placement et de réduire la contention inter-cœurs.
Ils ont épinglé le service à moins de cœurs par nœud NUMA et ont mis en place plus de réplicas au lieu d’essayer « d’utiliser tous les cœurs ».
Contre-intuitif, mais cela a récupéré le p99. Plus tard, ils ont retravaillé les structures de données pour être plus amicales au cache et réduit le parcours de pointeurs.
L’élément post-mortem qui a compté : les tests d’acceptation performance doivent inclure des métriques de misses/IPC, pas seulement le débit.
« Utilisation CPU » seule est une bague de humeur, pas un diagnostic.
Mini-récit 2 : L’optimisation qui a mal tourné (Direct I/O : le page cache n’était pas le méchant)
Une autre entreprise exécutait un pipeline qui ingérait des événements, les écrivait sur disque et les compactait périodiquement.
Ils ont vu une pression mémoire et ont décidé que le page cache Linux « volait » la RAM à l’application.
Un ingénieur a actionné un interrupteur : utiliser Direct I/O pour les écritures et lectures afin « d’éviter de polluer le cache ».
Les graphiques ont semblé excellents pendant un jour. L’utilisation mémoire s’est stabilisée. Puis les alarmes de latence ont commencé.
Pas partout—juste au pire endroit possible : les jobs de compaction et certains chemins de lecture qui dépendaient de relectures récentes.
Avec Direct I/O, ces lectures contournaient complètement le page cache. Chaque re-lecture devenait une opération de stockage.
NVMe est rapide, mais pas « aussi rapide que la RAM qui fait semblant d’être un disque ».
Le retour de bâton est venu avec des éclats supplémentaires. Sans lissage du page cache, l’I/O est devenu plus en rafales.
La profondeur de file a grimpé. Les latences de queue ont grimpé avec elle. Le CPU n’était toujours pas le goulet ; il passait juste plus de temps en iowait.
L’équipe avait optimisé les graphiques mémoire au prix de la latence visible client.
La solution finale a été ennuyeuse : revenir sur le Direct I/O pour les chemins de lecture chauds, le garder uniquement là où les données étaient vraiment froides ou séquentielles,
et définir des limites cgroup mémoire sensées pour que le page cache ne puisse pas affamer le processus.
Ils ont aussi introduit un petit cache applicatif pour les métadonnées afin d’éviter des lectures aléatoires répétées.
Leçon appris : « contourner les caches » n’est pas une stratégie de performance. C’est une arme. Utilisez-la seulement quand vous comprenez ce qu’elle touchera.
Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise (mesurer l’ensemble de travail et planifier le cache)
Une société de services financiers avait une charge hybride batch + API. Ils planifiaient un renouvellement : génération CPU plus récente, même taille RAM,
même stockage. L’équipe SRE a insisté pour un profil pré-migration : mesurer la taille de l’ensemble de travail, les ratios de hit cache, et le comportement NUMA
pendant la semaine de pic, pas l’heure de pic.
Ce n’était pas populaire car cela retardait le projet. Il fallait capturer des stats perf, taux de fautes de pages, et histogrammes de latence du stockage.
Il fallait aussi un test de charge ressemblant à la production, pas à une brochure de benchmark.
L’équipe a insisté quand même, car ils avaient déjà été brûlés.
Le profil a montré une falaise prévisible : une fois le dataset au-delà d’un certain point, le taux de hit du buffer cache chutait et les lectures touchaient le disque. Le p99 explosait.
Le système actuel survivait parce qu’il avait un cache effectif légèrement plus grand (buffer pool DB + cache OS) que la configuration prévue.
Le CPU « plus rapide » n’aurait pas aidé quand les misses de cache commençaient à atteindre le disque.
Ils ont ajusté le plan de renouvellement : plus de RAM par nœud, légèrement moins de cœurs, et une stratégie claire de placement NUMA.
Ils ont aussi programmé une routine pour garder les partitions les plus chaudes en cache pendant les heures d’activité.
Le jour du lancement fut calme, ce qui est le plus grand compliment en opérations.
Leçon : la mesure ennuyeuse bat le remplacement excitant. Les meilleurs incidents sont ceux qui n’arrivent jamais.
Erreurs courantes (symptômes → cause racine → correction)
1) Symptom: CPU is high, throughput is flat
Cause racine : Charge liée à la mémoire ; IPC faible dû aux misses de cache ou aux stalls backend.
Correction : Utilisez perf pour confirmer IPC bas et misses LLC élevés. Réduisez l’ensemble de travail, améliorez la localité des données, shardez, ou choisissez un CPU avec plus de cache par cœur.
2) Symptom: p99 got worse after “faster CPU” rollout
Cause racine : La topologie du cache/LLC par cœur a changé ; le placement NUMA a changé ; le comportement du préfetch diffère ; plus de contention par socket.
Correction : Comparez les compteurs perf ancien vs nouveau sous charge identique. Épinglez aux nœuds NUMA, réduisez le partage de cœurs, réévaluez la taille d’instance et le placement.
3) Symptom: Lots of iowait, but disks don’t look “busy” by throughput
Cause racine : Saturation de latence : I/O aléatoire petit, files d’attente élevées, ou latence tail storage ; le débit le masque.
Correction : Utilisez iostat -x et regardez await et aqu-sz. Réduisez les lectures aléatoires via la mise en cache, corrigez les requêtes/index, ou ajoutez des dispositifs/capacité IOPS.
4) Symptom: Performance drops when adding cores
Cause racine : Contention de cache partagé, contention de verrous, false sharing, ou saturation bande passante mémoire.
Correction : Scale out plutôt que up ; épinglez les threads ; éliminez le false sharing ; réduisez l’état mutable partagé ; mesurez avec perf et flame graphs si possible.
5) Symptom: “Optimization” reduces memory usage but increases latency
Cause racine : Contournement des caches (Direct I/O), réduction des buffer pools, ou éviction agressive menant à des lectures stockage.
Correction : Restaurez la mise en cache pour les chemins chauds, dimensionnez correctement la mémoire, et mesurez les ratios de hit cache et les fautes majeures.
6) Symptom: Periodic latency spikes every few seconds/minutes
Cause racine : Pauses GC, cycles d’éviction de cache, compaction, ou reclaim en arrière-plan (kswapd/reclaim direct).
Correction : Vérifiez majflt, signaux vmstat de reclaim, et logs GC. Réduisez le churn d’allocation, tunez le heap, ajoutez de la RAM, ou lissez la planification des compactions.
7) Symptom: A single host is slower than its identical siblings
Cause racine : Paramètres BIOS différents, politique d’alimentation, microcode, population mémoire (canaux), ou voisin bruyant saturant LLC/bande passante mémoire.
Correction : Comparez lscpu, governors, NUMA, et stats perf entre hôtes. Standardisez firmware et tunables noyau ; isolez les charges bruyantes.
8) Symptom: High system CPU, low user CPU, “nothing” in app profiles
Cause racine : Surcharge noyau : fautes de pages, pression sur la pile réseau, changements de contexte, ou churn de métadonnées filesystem.
Correction : Utilisez vmstat, pidstat, perf top. Réduisez les appels système (batching), tunez le logging, corrigez le churn de fichiers, et supprimez les points de synchronisation accidentels.
Checklists / plan étape par étape
Étape par étape : décider si le cache bat le CPU pour votre charge
- Collecter un profil proche de la production : mix QPS, concurrence, taille du dataset, et latences p50/p95/p99.
- Mesurer IPC et misses de cache sous charge : perf stat (cycles, instructions, LLC misses, stalled cycles).
- Mesurer la distribution de latence du stockage : iostat -x et timings au niveau applicatif.
- Mesurer les fautes majeures : pidstat -r et /proc/vmstat ; confirmez si les lectures touchent le disque.
- Valider le placement NUMA : numastat, placement des threads, et politique d’affinité.
- Vérifier le scaling : lancez sur 1, 2, 4, 8, N cœurs et voyez si le débit scale ou stagne.
- Changer une chose à la fois : épinglage, taille de buffer pool, taille du cache, disposition des données, puis retester.
- Seulement ensuite décidez du matériel : plus de cache par cœur, plus de RAM, mémoire plus rapide, ou plus de nœuds.
Checklist opérationnelle : avant un déploiement de « mise à niveau CPU »
- Baseliner les compteurs perf (IPC, LLC misses, stalls) sur l’ancien matériel.
- Confirmer les paramètres BIOS : politique power/performance, SMT, interleaving mémoire.
- Confirmer la parité du noyau et du microcode entre ancien et nouveau.
- Exécuter un test de charge avec la taille réelle du dataset et une skew d’accès réaliste.
- Comparer la latence tail, pas seulement le débit.
- Planifier un rollback qui ne nécessite pas de réunion.
Checklist stockage/cache : faire fonctionner la mise en cache pour vous
- Connaître la taille de votre ensemble de travail (données chaudes) et la comparer à la RAM/aux caches.
- Préférer les accès séquentiels quand c’est possible ; l’I/O aléatoire est une taxe.
- Garder les index et métadonnées chaudes en mémoire ; le disque est pour la vérité froide, pas les opinions chaudes.
- Mesurer les ratios de hit cache et les taux d’éviction, ne pas deviner.
- Ne pas « optimiser » en désactivant la sécurité (barrières d’écriture, journalisation) à moins d’assumer la perte de données.
FAQ
1) Le cache CPU est-il vraiment si important, ou est-ce juste du drama de nerds performance ?
C’est important. De nombreuses charges réelles sont limitées par la latence mémoire. Un petit changement du taux de miss LLC peut faire varier la latence p99 bien plus
qu’un gain de fréquence CPU modeste.
2) Si mon utilisation CPU est à 90%, cela ne prouve-t-il pas que je suis lié au CPU ?
Non. Une haute utilisation CPU peut inclure des cycles bloqués, des boucles de spin, de la contention sur des verrous, et des frais noyau. Utilisez IPC et métriques de stall
(perf stat) pour séparer « exécution » et « attente coûteuse ».
3) Quelle est la métrique la plus simple pour dire « le monstre du cache gagne » ?
IPC plus misses LLC sous charge production. Si l’IPC est bas et les misses LLC élevés, vous n’êtes pas en manque de CPU ; vous manquez de localité et de hits de cache.
4) Plus de RAM résout-il toujours les problèmes de cache ?
Plus de RAM aide si l’ensemble de travail peut tenir et rester chaud (page cache, buffer pool, ARC). Mais la RAM ne corrige pas des patterns d’accès pathologiques, le false sharing, ou un mauvais placement NUMA.
5) Dois-je épingler les processus aux cœurs ?
Parfois. L’épinglage peut améliorer la chaleur du cache et réduire les migrations. Mais il peut aussi aggraver le déséquilibre de charge.
Testez avec une charge réelle. Si l’épinglage améliore le p99 et réduit les misses LLC, conservez-le.
6) Pourquoi ma mise à niveau NVMe n’a-t-elle pas beaucoup amélioré la latence ?
Parce que vous touchiez déjà le page cache ou buffer cache, ou parce que votre latence était dominée par des stalls CPU, des verrous, ou des sauts réseau.
Aussi, NVMe peut être rapide et avoir une latence tail désagréable sous saturation.
7) Direct I/O n’est-il pas plus rapide car il évite le double caching ?
Ça peut l’être pour des charges spécifiques (grosses lectures/écritures séquentielles, streaming). Pour des patterns mixtes ou avec des relectures fréquentes, le page cache
est une fonctionnalité de performance. Le supprimer transforme souvent la « vitesse mémoire » en « vitesse stockage ».
8) Comment savoir si NUMA me nuit ?
Si vous avez des systèmes multi-socket, supposez que NUMA compte. Confirmez avec numastat et perf. Les symptômes incluent un mauvais scaling, une latence accrue, et des différences
de performance selon l’endroit où le scheduler place les threads.
9) Un CPU avec moins de cœurs peut-il battre un autre avec plus de cœurs ?
Oui, si le CPU avec moins de cœurs a plus de cache par cœur, une meilleure latence mémoire, ou évite la saturation de bande passante. Beaucoup de services ne scalent pas linéairement
avec les cœurs parce que l’accès aux données et la contention dominent.
10) Quelle est l’erreur de cache la plus courante dans le code applicatif ?
Des structures de données qui détruisent la localité : graphes riches en pointeurs, itérations de hachage aléatoires, et allocation de millions de petits objets.
Aussi : le false sharing dans des compteurs et files multithread.
Prochaines étapes pratiques
Si vous retenez une leçon opérationnelle : cessez de traiter la vitesse CPU comme le rôle principal. En production, c’est un acteur secondaire. Les rôles principaux sont la localité, la mise en cache et la latence.
- Exécutez
perf statsous vraie charge et enregistrez IPC, misses LLC, et cycles bloqués. - Exécutez
iostat -xet regardez la latence et la profondeur de file, pas seulement les MB/s. - Vérifiez les fautes majeures et le comportement de reclaim pour voir si vous basculez de la mémoire vers le disque.
- Validez le placement NUMA et testez l’épinglage comme expérience, pas comme dogme.
- Ce n’est qu’après ces mesures que vous décidez : plus de cache (autre SKU CPU), plus de RAM, meilleure disposition des données, ou une architecture différente (scale out).
Ensuite faites la chose ennuyeuse : documentez la baseline, codifiez les vérifications dans votre runbook de déploiement, et faites de la « cache et localité » une exigence de performance de première classe.
Votre futur vous, coincé sur un bridge d’incident à 2 h du matin, vous en sera étrangement reconnaissant.