P-states et C-states : ce que fait votre CPU lorsqu’il est inactif

Le rapport d’incident commence toujours de la même manière : « Le CPU n’était qu’à 12 % et pourtant la latence a grimpé. »
Vous regardez les tableaux de bord, voyez beaucoup de marge, puis quelqu’un suggère « peut-être que la base de données est lente ».
Pendant ce temps, le vrai coupable est plus discret : le CPU économise de l’énergie si agressivement que le réveil vous coûte des millisecondes non budgétisées.

Les P-states et les C-states sont les boutons et les engrenages derrière « inactif ». Ce sont aussi la raison pour laquelle un serveur peut être « majoritairement inactif »
et pourtant donner l’impression d’avancer dans de la mélasse quand arrive la prochaine rafale de travail. Si vous exploitez des systèmes de production,
vous n’avez pas besoin de devenir microarchitecte CPU — mais il vous faut suffisamment de littératie opérationnelle pour éviter de marcher sur des mines.

Un modèle mental orienté production : P-states vs C-states

Pensez à votre CPU comme ayant deux sortes de « modes » :

  • P-states (états de performance) : le CPU est en fonctionnement, mais à différents points fréquence/tension.
    Un P-state de performance plus élevé implique généralement une fréquence et une consommation plus élevées. Un P-state plus bas signifie plus lent mais moins coûteux.
  • C-states (états d’inactivité) : le CPU n’exécute pas d’instructions utiles.
    Plus l’état C est profond, plus de parties du cœur (et parfois du package entier) peuvent être mis hors tension, économisant de l’énergie — mais le réveil prend plus de temps.

Voilà la version simple. La version opérationnelle ajoute deux notes que vous devriez tatouer dans vos runbooks :

  • Les P-states ne sont pas un bouton « régler et oublier ». Le système d’exploitation, le microcode et le CPU lui-même peuvent influencer la sélection de fréquence.
    Sur les systèmes Intel modernes, le matériel peut prendre beaucoup de décisions même si vous pensez que Linux est aux commandes.
  • Les C-states peuvent être par cœur et par package. Un seul cœur bruyant peut empêcher la prise d’un sommeil profond pour l’ensemble du socket.
    Inversement, un système « calme » peut entrer si profondément en sommeil que la requête suivante paie une pénalité de réveil désagréable.

Un raccourci mental supplémentaire : les P-states concernent « à quelle vitesse quand on travaille ». Les C-states concernent « à quel point endormi en attendant ».
La plupart des incidents surviennent lorsque vous optimisez l’un et oubliez l’autre.

Ce que le CPU fait réellement lorsqu’il est « inactif »

Quand Linux n’a rien d’exécutable pour un CPU, il lance une boucle idle. La boucle idle ne se contente pas de tourner
(sauf si vous la forcez). Elle émet typiquement une instruction comme HLT (halt) ou utilise des mécanismes plus avancés
qui permettent au CPU d’entrer dans des états de sommeil plus profonds.

États C du cœur : C0 jusqu’à « assez profond pour vous ennuyer »

C0 signifie « actif ». Tout le reste est une saveur de « n’exécute pas d’instructions ». Le mappage exact varie selon les fournisseurs,
mais le modèle opérationnel est cohérent :

  • C1 : sommeil léger. Sortie rapide. Économies d’énergie minimales.
  • C1E : C1 amélioré ; réduit souvent la tension plus agressivement.
  • C3 : sommeil plus profond ; plus d’horloges internes coupées ; latence de sortie plus élevée.
  • C6/C7 et apparentés : sommeil très profond ; peut vider des caches, mettre hors tension des parties du cœur ; la latence de sortie devient mesurable.

La latence de sortie est la taxe cachée. Si votre charge est en rafales et sensible à la latence, les C-states profonds peuvent transformer
« majoritairement inactif » en « périodiquement lent ».

États C du package : tout le socket fait la sieste

Les états C du package (souvent étiquetés PC2/PC3/PC6/PC10) contiennent les grandes économies d’énergie.
Ils contiennent aussi les surprises. Le package ne peut aller en profond que si certaines conditions sont remplies :

  • Tous les cœurs sont suffisamment inactifs.
  • Les composants uncore (LLC, contrôleur mémoire, interconnect) peuvent être mis en horloge/puissance réduite.
  • Les périphériques et le firmware conviennent que c’est sûr.

En environnement serveur, une seule source d’interruptions bavarde, un timer tick, ou une politique d’alimentation mal configurée peut bloquer les états package profonds.
Ou, à l’inverse : le sommeil package profond est autorisé, et votre latence de queue commence à faire des danses interprétatives.

P-states : la sélection de fréquence n’est plus un seul bouton

L’ancienne histoire disait : l’OS sélectionne une fréquence dans une table ; le CPU l’exécute. L’histoire moderne : l’OS définit des politiques et des indices,
et la logique interne du CPU réalise souvent des boucles de contrôle rapides. Le pilote intel_pstate d’Intel, le CPPC d’AMD,
et les P-states gérés par le matériel estompent la ligne entre « gouverneur » et « firmware ».

Le turbo complique encore les choses. Le turbo n’est pas « une fréquence ». C’est un ensemble de comportements de boost opportunistes
limités par la puissance, la température, le courant et le nombre de cœurs actifs. Votre monitoring peut indiquer « 3.5 GHz »
pendant que le CPU effectue des boosts par cœur qui varient microseconde après microseconde.

Blague n°1 : si vous voulez un jour vous sentir impuissant, essayez de discuter avec un CPU sur ce que signifie « fréquence maximale ».

Pourquoi vous devriez vous en préoccuper : latence, jitter, débit et coûts

Latence et latence de queue

Les C-states profonds ajoutent une latence de réveil. L’échelle de fréquence ajoute une latence de montée en puissance. Généralement, il s’agit de microsecondes à quelques millisecondes,
ce qui paraît faible jusqu’à ce que vous exécutiez :

  • des services RPC avec des SLO stricts (le p99 compte, pas la moyenne)
  • des backends de stockage où le temps de complétion IO est visible pour l’utilisateur
  • des bases de données avec contention sur des verrous où de petits délais s’amplifient
  • des systèmes de trading à faible latence où le jitter est un risque professionnel

En d’autres termes : si votre système est « inactif la plupart du temps » mais doit répondre rapidement quand il n’est pas inactif, vous devez vous en préoccuper.

Débit et performance soutenue

Les P-states et le turbo déterminent combien de travail vous effectuez par watt. Mais le turbo est borné par des limites de puissance (PL1/PL2 sur Intel),
la thermique et les contraintes de plateforme. Si vous forcez le « mode performance » partout, vous gagnerez peut-être des benchmarks courts mais perdrez en débit soutenu
car vous atteindrez des plafonds thermiques/power et serez fortement throttlés.

Puissance, refroidissement et argent réel

Si vous opérez à grande échelle, la politique de puissance CPU change l’histoire du datacenter : électricité, refroidissement et densité de racks.
Même si vous ne payez pas directement la facture d’électricité, vous la paierez lors de la planification de capacité.

Voici la vérité cynique SRE : vous ne pouvez pas dépenser pour résoudre le jitter de latence si la configuration de votre flotte est incohérente.
Et vous ne pouvez pas ajuster indéfiniment une politique d’alimentation si votre application tourne en rond à faire du CPU en attendant.

Faits intéressants et courte histoire (parce que ce bazar a des racines)

  • ACPI a standardisé les états d’alimentation pour que les systèmes d’exploitation puissent gérer l’énergie à travers les fournisseurs plutôt que des interfaces BIOS sur mesure.
  • Les CPU de l’ère « SpeedStep » ont rendu la mise à l’échelle des fréquences grand public ; avant cela, « gestion d’alimentation » signifiait surtout « éteindre l’écran ».
  • Le turbo moderne est limité par la puissance, pas par la fréquence : les CPU cherchent des enveloppes de puissance et thermique, pas un horloge fixe.
  • Les C-states précèdent le cloud, mais le cloud a rendu leurs compromis douloureux : les charges multi-tenant sont rafales et imprévisibles.
  • Les noyaux sans tick (NO_HZ) ont réduit les interruptions périodiques pour laisser les CPU inactifs plus longtemps et atteindre des C-states plus profonds.
  • Intel a introduit le contrôle P-state géré par le matériel pour réagir plus vite que la boucle du scheduler de l’OS.
  • RAPL (Running Average Power Limit) a donné aux logiciels un moyen de mesurer/limiter l’énergie CPU, faisant de la puissance une métrique de première classe.
  • Les C-states de package sont devenus critiques à mesure que la consommation uncore (LLC, contrôleur mémoire, interconnect) a commencé à rivaliser avec la consommation des cœurs.
  • La virtualisation a tout compliqué : l’« inactivité » d’un invité n’est pas nécessairement une inactivité hôte ; le halt dans une VM implique une politique d’hyperviseur.

Comment Linux contrôle les P-states et les C-states

Le plan de contrôle : pilotes, gouverneurs et politiques

Sous Linux, la mise à l’échelle de la fréquence CPU est généralement gérée par le sous-système cpufreq. Deux pilotes courants :

  • intel_pstate (Intel) : souvent le défaut sur les Intel modernes. Peut fonctionner en mode « actif » où le CPU participe fortement aux décisions.
  • acpi-cpufreq : pilote ACPI plus traditionnel avec des tables de fréquences explicites.

Les gouverneurs sont des politiques comme performance, powersave et (selon le pilote) schedutil.
Ne traitez pas les noms de gouverneurs comme des vérités universelles ; leur comportement varie selon le pilote.

Le plan d’inactivité : cpuidle, pilotes C-state et contraintes de latence

Les C-states sous Linux sont gérés par le sous-système cpuidle. Il choisit un état d’inactivité en fonction de :

  • la durée prédite d’inactivité (combien de temps avant le prochain événement qui réveille le CPU)
  • la latence de sortie de chaque état d’inactivité
  • les contraintes QoS (indices de sensibilité à la latence depuis le kernel/utilisateurspace)
  • ce que la plateforme et le firmware autorisent

BIOS/UEFI : l’endroit où « on changera juste un paramètre » devient une légende

Les réglages du firmware peuvent outrepasser ou contraindre tout le reste :

  • État C maximal autorisé (ex. limiter à C1)
  • Limites d’état C du package
  • Activation/désactivation du turbo
  • Energy/Performance Bias (EPB d’Intel)
  • « Profils d’alimentation » du fournisseur qui font plusieurs choses à la fois

En production, le mode d’échec le plus fréquent n’est pas « mauvais noyau ». C’est « valeurs par défaut BIOS différentes entre lots ».

Une citation fiabilité (idée paraphrasée)

Idée paraphrasée attribuée à John Ousterhout : la complexité est la cause première de nombreux problèmes de fiabilité.
La gestion d’alimentation, c’est de la complexité avec un wattmètre.

Tâches pratiques : commandes, sens des sorties et décisions

Le seul réglage qui compte est celui que vous pouvez vérifier. Ci-dessous des tâches réelles que j’attends d’un ingénieur en astreinte,
avec commandes, exemples de sorties et la décision à prendre.

Task 1: Identify the active CPU frequency driver and governors

cr0x@server:~$ cpupower frequency-info
analyzing CPU 0:
  driver: intel_pstate
  CPUs which run at the same hardware frequency: 0
  available cpufreq governors: performance powersave
  current policy: frequency should be within 800 MHz and 3900 MHz.
                  The governor "powersave" may decide which speed to use
  current CPU frequency: 1200 MHz (asserted by call to hardware)
  boost state support:
    Supported: yes
    Active: yes

Ce que cela signifie : Vous êtes sur intel_pstate ; les choix de gouverneur sont limités et le comportement dépend du pilote.
La fréquence actuelle est basse parce que la politique l’autorise. Le turbo est activé.

Décision : Si vous diagnostiquez des pics de latence, notez que powersave sous intel_pstate peut toujours booster,
mais les caractéristiques de montée en fréquence diffèrent. Ne changez pas de gouverneur à l’aveugle ; mesurez d’abord.

Task 2: Check min/max frequency policy per CPU

cr0x@server:~$ for f in /sys/devices/system/cpu/cpu*/cpufreq/scaling_{min,max}_freq; do echo "$f: $(cat $f)"; done | head
/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq: 800000
/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq: 3900000
/sys/devices/system/cpu/cpu1/cpufreq/scaling_min_freq: 800000
/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq: 3900000

Ce que cela signifie : Les bornes de politique OS sont larges. Si la performance est encore mauvaise, la limitation est ailleurs
(caps de puissance, thermique, états C, contention).

Décision : Si scaling_max_freq est anormalement bas, suspectez un profil de tuning, des contraintes du runtime de conteneur,
ou des événements de limite de plateforme.

Task 3: Inspect turbo/boost status

cr0x@server:~$ cat /sys/devices/system/cpu/cpufreq/boost
1

Ce que cela signifie : Turbo/boost est activé.

Décision : Pour des services sensibles à la latence, le turbo aide souvent (temps de service plus court).
Pour une latence déterministe, le turbo peut ajouter de la variabilité thermique ; considérez verrouiller la politique seulement après mesure.

Task 4: Verify CPU idle state availability and residency (core C-states)

cr0x@server:~$ sudo cpupower idle-info
CPUidle driver: intel_idle
CPUidle governor: menu
analyzing CPU 0:
  Number of idle states: 4
  Available idle states: POLL C1 C1E C6
  C1: exit latency 2 us
  C1E: exit latency 10 us
  C6: exit latency 85 us

Ce que cela signifie : Un C6 profond existe avec ~85 µs de latence de sortie (exemple). Ce n’est pas catastrophique, mais ce n’est pas gratuit.

Décision : Si vos p99 corrèlent avec des périodes d’inactivité, envisagez de limiter l’état C le plus profond seulement sur les nœuds affectés et retestez.

Task 5: Check per-state time and usage counts for idle states

cr0x@server:~$ for s in /sys/devices/system/cpu/cpu0/cpuidle/state*; do \
  echo "$(basename $s) name=$(cat $s/name) disable=$(cat $s/disable) time=$(cat $s/time) usage=$(cat $s/usage)"; \
done
state0 name=POLL disable=0 time=122 usage=18
state1 name=C1 disable=0 time=983421 usage=24011
state2 name=C1E disable=0 time=221934 usage=9120
state3 name=C6 disable=0 time=55290321 usage=110432

Ce que cela signifie : CPU0 passe beaucoup de temps en C6. C’est bon pour la consommation. Cela peut être mauvais pour la latence de réveil.

Décision : Si vous observez une latence de queue, c’est votre candidat à l’enquête. Ensuite, corrélez avec les métriques applicatives et les interruptions.

Task 6: Check package C-state residency (Intel, via turbostat)

cr0x@server:~$ sudo turbostat --Summary --quiet --show PkgWatt,PkgTmp,Pkg%pc2,Pkg%pc6,Pkg%pc10 --interval 1 --num_iterations 3
PkgWatt  PkgTmp  Pkg%pc2  Pkg%pc6  Pkg%pc10
  32.15     54      2.12     8.41     61.77
  28.02     52      1.88     7.96     68.10
  35.44     55      2.30     9.02     58.33

Ce que cela signifie : Le package atteint fréquemment PC10 (sommeil profond). La puissance est basse. Excellente efficacité.
C’est aussi une cause classique de « latence de démarrage à froid » au réveil.

Décision : Si vous exécutez des charges à faible latence, envisagez de limiter les C-states du package ou d’utiliser un profil tuned faible latence sur ces nœuds.
Si vous exécutez des jobs batch, célébrez et passez à autre chose.

Task 7: Look for power limit and throttling signals (Intel RAPL / thermal)

cr0x@server:~$ sudo turbostat --quiet --show Bzy_MHz,Avg_MHz,Busy%,CoreTmp,PkgTmp,PkgWatt,CorWatt,GFXWatt --interval 1 --num_iterations 2
Bzy_MHz  Avg_MHz  Busy%  CoreTmp  PkgTmp  PkgWatt  CorWatt  GFXWatt
   4200     1850  22.15       72      79    165.2     92.1     0.0
   4100     1902  23.40       74      81    165.0     93.0     0.0

Ce que cela signifie : Des fréquences boost existent, mais la puissance du package est élevée. Si les températures augmentent, vous risquez de throttler bientôt.

Décision : Si la performance est incohérente en charge, inspectez le refroidissement, les limites de puissance et le comportement du turbo soutenu avant d’accuser le noyau.

Task 8: Confirm kernel tick mode and timer behavior (idle disruption)

cr0x@server:~$ grep -E 'NO_HZ|CONFIG_HZ' -n /boot/config-$(uname -r) | head -n 5
114:CONFIG_HZ=250
501:CONFIG_NO_HZ_COMMON=y
504:CONFIG_NO_HZ_IDLE=y
507:CONFIG_NO_HZ_FULL is not set

Ce que cela signifie : Le mode sans tick est activé (NO_HZ_IDLE), ce qui aide les C-states profonds. Pas complètement sans tick.

Décision : Si votre charge nécessite une latence cohérente, vous préférerez peut-être moins de transitions d’inactivité profondes (politique), pas nécessairement modifier la config du noyau.

Task 9: Identify interrupt hotspots that prevent idle or cause wake storms

cr0x@server:~$ sudo cat /proc/interrupts | head -n 15
           CPU0       CPU1       CPU2       CPU3
  0:         21         18         19         22   IO-APIC   2-edge      timer
  1:          0          0          0          0   IO-APIC   1-edge      i8042
 24:     883421     102331      99321      90122   PCI-MSI  327680-edge  eth0-TxRx-0
 25:     112331     843221     121112     110998   PCI-MSI  327681-edge  eth0-TxRx-1

Ce que cela signifie : Les queues NIC sont lourdes sur certains CPU. Cela peut empêcher les cœurs de s’endormir et provoquer des réveils en rafales.

Décision : Envisagez de régler l’affinité IRQ (ou le comportement de irqbalance) si vous voyez des hotspots mono-cœur ou des pics de latence alignés avec des interruptions.

Task 10: Check irqbalance status and whether it’s fighting your pinning

cr0x@server:~$ systemctl status irqbalance --no-pager
● irqbalance.service - irqbalance daemon
     Loaded: loaded (/lib/systemd/system/irqbalance.service; enabled; preset: enabled)
     Active: active (running) since Tue 2026-01-10 08:21:10 UTC; 2h 12min ago
       Docs: man:irqbalance(1)
   Main PID: 912 (irqbalance)

Ce que cela signifie : irqbalance est actif. Bon réglage par défaut — sauf si vous effectuez un pinning manuel des IRQ pour la faible latence et avez oublié de le désactiver.

Décision : Si vous avez besoin d’une isolation stricte des CPU, configurez soit des CPUs bannis pour irqbalance soit désactivez-le et gérez les affinités explicitement.

Task 11: See if a tuning profile is enforcing aggressive power savings

cr0x@server:~$ tuned-adm active
Current active profile: virtual-guest

Ce que cela signifie : Un profil tuned est actif, modifiant potentiellement le gouverneur CPU et d’autres réglages impactant la latence.

Décision : Pour un hôte de base de données ou un service RPC sensible à la latence, testez latency-performance (ou le profil recommandé par le fournisseur) sur un nœud canari.

Task 12: Verify current governor quickly across all CPUs

cr0x@server:~$ grep -H . /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor 2>/dev/null | head
/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor:powersave
/sys/devices/system/cpu/cpu1/cpufreq/scaling_governor:powersave
/sys/devices/system/cpu/cpu2/cpufreq/scaling_governor:powersave
/sys/devices/system/cpu/cpu3/cpufreq/scaling_governor:powersave

Ce que cela signifie : Tous les CPU sont en powersave.

Décision : Si vous poursuivez des régressions de latence, basculez un hôte unique en performance temporairement et mesurez le p99. Ne déployez pas sur toute la flotte selon des impressions.

Task 13: Temporarily change governor (and understand what you’re risking)

cr0x@server:~$ sudo cpupower frequency-set -g performance
Setting cpu: 0
Setting cpu: 1
Setting cpu: 2
Setting cpu: 3

Ce que cela signifie : Vous avez demandé le gouverneur performance. Sur intel_pstate, cela change le comportement de la politique, pas une horloge fixe.

Décision : Utilisez cela comme expérience contrôlée. Si la latence s’améliore sensiblement et que le budget énergétique le permet, envisagez un profil tuned plutôt que des modifications ad hoc.

Task 14: Limit deepest C-state (surgical test, not a lifestyle)

cr0x@server:~$ echo 1 | sudo tee /sys/devices/system/cpu/cpu0/cpuidle/state3/disable
1

Ce que cela signifie : Vous avez désactivé un état d’inactivité (ici, state3 peut être C6). Cela force un sommeil plus superficiel sur CPU0.

Décision : Si le p99 s’améliore et que la consommation augmente de façon acceptable, appliquez via une méthode persistante (args du noyau, tuned, ou unité systemd) et documentez-le.

Task 15: Check for virtualization effects: are you tuning the guest while the host decides?

cr0x@server:~$ systemd-detect-virt
kvm

Ce que cela signifie : Vous êtes dans une VM. Les réglages d’alimentation du guest peuvent avoir un effet limité ; la politique hôte et l’ordonnancement de l’hyperviseur pèsent davantage.

Décision : Si vous avez besoin d’un comportement faible latence, travaillez avec l’équipe plateforme : pinning CPU, gouverneur hôte et politique C-state sont les vrais leviers.

Task 16: Check CPU pressure and scheduling contention (because “idle” can be a lie)

cr0x@server:~$ cat /proc/pressure/cpu
some avg10=0.00 avg60=0.10 avg300=0.08 total=18873412
full avg10=0.00 avg60=0.00 avg300=0.00 total=0

Ce que cela signifie : La pression CPU est faible ; le scheduler n’est pas en difficulté. Si la latence est mauvaise, concentrez-vous sur le comportement réveil/sommeil, les interruptions, l’IO ou la contention sur les verrous.

Décision : Si some ou full est élevé, ne traquez pas d’abord les C-states — corrigez la contention, les limites CPU ou les voisins bruyants.

Feuille de diagnostic rapide

Voici l’ordre que j’utilise quand quelqu’un dit « la latence pique quand la machine est majoritairement inactive » ou « le CPU est bas mais c’est lent ».
Il est optimisé pour trouver le goulot rapidement, pas pour vous faire sentir malin.

Première étape : décidez si vous poursuivez un comportement d’alimentation CPU ou autre chose

  1. Vérifiez la pression CPU (contention du scheduler) :
    si PSI est élevé, vous n’êtes pas « inactif », vous êtes sursouscrit ou throttlé.
  2. Vérifiez la file d’exécution et le steal time (surtout en VM) :
    une faible utilisation peut coexister avec une latence élevée si vous attendez d’être ordonnancé.
  3. Vérifiez l’iowait et la latence de stockage :
    beaucoup de « CPU inactif » est en réalité « bloqué sur de l’IO ».

Deuxième étape : confirmez la politique d’alimentation active

  1. Pilote + gouverneur via cpupower frequency-info.
  2. Turbo activé ? via /sys/devices/system/cpu/cpufreq/boost.
  3. Profil tuned ou service fournisseur imposant la politique.

Troisième étape : mesurez la résidence C-state et les perturbations liées au réveil

  1. Utilisation/temps des C-states du cœur via /sys/.../cpuidle ou cpupower idle-info.
  2. C-states du package via turbostat (si disponible).
  3. Hotspots d’interruptions via /proc/interrupts et outils d’affinité IRQ.

Faites un changement à la fois, sur un nœud, avec un timer

La façon la plus rapide de perdre une semaine est d’alterner paramètres BIOS, arguments noyau et profils tuned dans la même fenêtre de maintenance.
Changez une chose, mesurez p95/p99 et puissance/thermique, puis décidez.

Trois mini-récits du monde de l’entreprise

Mini-récit 1 : L’incident causé par une mauvaise hypothèse

Une équipe a déployé une nouvelle passerelle API interne « légère ». Elle était efficace : CPU faible, courtes rafales, très axée sur les interruptions réseau.
Sur les tableaux de bord, l’utilisation CPU oscillait autour de 15–25 %. Tout le monde se félicitait de ne pas surprovisionner.
Puis le p99 a doublé pendant les heures creuses, exactement quand le trafic devenait plus calme.

La première hypothèse était classique : « moins de charge signifie plus de marge ». Mais le service était en rafales.
Pendant les périodes calmes, les CPU entraient en C-states profonds du package. Quand la rafale suivante arrivait, le traitement des requêtes payait la latence de réveil
plus la latence de montée en fréquence. Individuellement petites, collectivement moches.

La deuxième hypothèse : « On est en gouverneur performance, donc la fréquence est élevée. » Ce n’était pas le cas.
La moitié de la flotte avait un profil BIOS différent. Ces hôtes autorisaient des états package plus profonds et avaient une politique plus orientée économie d’énergie.
La flotte était hétérogène, et le load balancer mélangeait joyeusement des hôtes aux comportements de réveil différents.

La correction n’a pas été héroïque. Ils ont standardisé les profils firmware, puis testé progressivement un profil tuned faible latence uniquement sur les nœuds passerelle.
Ils ont gardé le sommeil profond activé pour les workers batch. La latence s’est stabilisée, la consommation est restée raisonnable, et l’incident s’est terminé non pas par un roman postmortem,
mais par une courte checklist ajoutée à la provision.

Mini-récit 2 : L’optimisation qui a mal tourné

Une équipe stockage (pensez : service de blocs distribué) voulait réduire la consommation. Ils ont forcé des C-states plus profonds et mis les gouverneurs en powersave sur
les nœuds de stockage. Sur le papier c’était responsable : les nœuds stockage « attendaient surtout de l’IO », et les CPU ne paraissaient pas occupés.

Ce qu’ils ont manqué, c’est la façon dont le stockage se comporte sous charges mixtes. Les chemins de complétion IO sont sensibles à la latence et pilotés par interruptions.
Le service restait calme, puis traitait soudainement une tempête de complétions, travaux de checksum et réponses réseau.
Avec des C-states profonds, la latence interruption→handler augmentait. Avec une mise à l’échelle agressive de la fréquence, les cœurs partaient lents puis montaient en cadence.

Le retour de bâton est apparu sous une forme étrange : la latence moyenne demeurait acceptable, mais la latence de queue et le jitter devenaient brutaux.
Les clients retentaient. Les retries causaient des micro-rafales. Les micro-rafales faisaient rebondir les CPU entre sommeil et réveil encore plus.
Le changement « économie d’énergie » a créé une boucle de rétroaction qui a gaspillé à la fois puissance et temps.

Ils ont annulé les changements sur les fronts de stockage mais gardé les économies d’énergie sur les nœuds de compactage en arrière-plan.
La leçon réelle fut la portée : la politique d’alimentation est spécifique à la charge. Appliquez-la par rôle, pas par cluster, et surveillez toujours le p99 et les taux de retries.

Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise

Une autre entreprise gérait une flotte Kubernetes avec des générations d’instances mixtes. Leur équipe plateforme a fait quelque chose de profondément peu sexy :
ils ont maintenu une matrice de capacités matérielles et un test de provision qui enregistraient la disponibilité des C-states, le statut turbo et la résidence d’inactivité.
Chaque nouvelle mise à jour BIOS devait passer les mêmes tests avant d’être autorisée dans l’image gold.

Un trimestre, une mise à jour firmware du fournisseur a changé les limites par défaut des C-states du package.
Rien n’a explosé immédiatement. C’est le truc — ce type de modification ne casse pas toujours bruyamment. Elle modifie juste les caractéristiques de latence.

Leurs tests l’ont détecté parce que la résidence C-state du package enregistrée a changé significativement sur les nœuds inactifs.
Ils n’ont pas eu besoin d’un client pour se plaindre d’abord. Ils ont suspendu le déploiement, ajusté la politique firmware et documenté la différence.

Le résultat fut ennuyeux : aucun incident. L’équipe plateforme n’a reçu aucun éloge.
Mais les équipes applicatives n’ont jamais eu à apprendre les C-states du package à 3h du matin, et c’est la plus haute forme de succès opérationnel.

Blague n°2 : le meilleur changement de gestion d’alimentation est celui qui n’apparaît jamais dans une diapositive postmortem.

Erreurs courantes (symptômes → cause racine → correction)

1) « Le CPU est bas mais le p99 est élevé »

Symptômes : faible utilisation CPU moyenne ; gros pics de latence de queue pendant les périodes calmes ; meilleure latence sous charge soutenue.

Cause racine : C-states profonds et/ou mise à l’échelle agressive de la fréquence provoquent des pénalités de réveil et de montée en fréquence ; le trafic en rafales déclenche des transitions répétées.

Correction : mesurez la résidence C-state (cœur et package). Testez un profil faible latence en canari ou limitez les C-states les plus profonds sur les nœuds affectés.
Confirmez la cohérence des profils BIOS sur la flotte.

2) « Fréquence bloquée basse même en charge »

Symptômes : cpupower frequency-info montre une fréquence actuelle basse ; débit inférieur aux attentes ; températures CPU modérées.

Cause racine : scaling_max_freq plafonné par la politique, interactions avec les quotas CPU des conteneurs, ou contraintes de limite de puissance de la plateforme.

Correction : vérifiez scaling_max_freq et le profil tuned ; confirmez le turbo ; inspectez les limites de puissance/throttling via turbostat.
Dans les conteneurs, vérifiez les quotas CPU et les affectations cpuset.

3) « Amélioration sur un nœud mais pas sur un autre »

Symptômes : même logiciel, latence différente ; changements de tuning fonctionnent de manière incohérente selon les hôtes.

Cause racine : valeurs par défaut firmware hétérogènes, différences de microcode, ou pilotes de fréquence différents (intel_pstate vs acpi-cpufreq).

Correction : standardisez les réglages BIOS ; assurez des paramètres noyau cohérents ; inventairez la sélection de pilote et les versions microcode.

4) « Des tempêtes IRQ empêchent l’inactivité et gaspillent l’énergie »

Symptômes : le package n’atteint jamais les C-states profonds ; watts d’inactivité élevés ; certains CPU ont des comptes d’interruptions énormes.

Cause racine : déséquilibre d’affinité d’interruptions, queues NIC mal configurées, périphériques bavards, ou comportement de timer.

Correction : inspectez /proc/interrupts ; réglez l’affinité IRQ ; configurez correctement le nombre de queues ; vérifiez la configuration d’irqbalance.

5) « J’ai désactivé les C-states et maintenant le débit a chuté »

Symptômes : la consommation a augmenté ; le throttling thermique apparaît ; la performance soutenue chute après un court boost initial.

Cause racine : supprimer les économies d’inactivité augmente la température/power de base, réduisant la marge turbo et causant du throttling.

Correction : ne désactivez pas massivement les états profonds. Utilisez des politiques par rôle. Surveillez les thermiques et la puissance du package ; visez la stabilité, pas les horloges maximales.

6) « On a isolé des CPU, mais la latence jitter persiste »

Symptômes : isolation CPU configurée ; on observe toujours du jitter ; queues longues occasionnelles.

Cause racine : la gestion d’alimentation continue de faire des transitions cœur/package ; des interruptions arrivent sur des CPUs isolés ; contention entre threads SMT.

Correction : alignez l’affinité IRQ avec l’isolation ; envisagez de limiter les C-states profonds pour les cœurs isolés ; vérifiez la politique SMT pour les charges critiques en latence.

Listes de vérification / plan pas à pas

Checklist A: Standardiser une baseline d’alimentation de flotte (la partie ennuyeuse qui prévient les surprises)

  1. Inventoriez les modèles CPU, versions microcode et statut de virtualisation à travers les nœuds.
  2. Consignez le pilote de fréquence (intel_pstate/acpi-cpufreq) et les gouverneurs dans la gestion de configuration.
  3. Consignez les réglages du BIOS/UEFI (limites C-state, turbo, EPB) par génération matérielle.
  4. Définissez des politiques par rôle : criticité latence, équilibré, batch/efficacité.
  5. Appliquez des profils tuned ou équivalents via l’automatisation ; pas de configurations artisanales uniques.
  6. Échantillonnez en continu la résidence C-state du package et les watts sur des canaris inactifs pour détecter les dérives après mises à jour firmware.

Checklist B: Ajuster un nœud de service sensible à la latence (en sécurité)

  1. Base : capturez p95/p99 de latence, taux d’erreur/retry, et consommation au repos et en rafale typique.
  2. Confirmez l’absence de contention CPU : vérifiez PSI CPU et files d’attente.
  3. Mesurez la résidence C-state du package à l’idle et pendant les rafales.
  4. Changement en canari : basculez le profil tuned ou le gouverneur sur un nœud.
  5. Si toujours spiky, testez la limitation de l’état C le plus profond (temporaire) et re-mesurez.
  6. Validez thermiques et limites de puissance soutenues ; surveillez le throttling.
  7. Déployez par rôle, pas par flotte. Documentez la politique avec le « pourquoi », pas seulement le « quoi ».

Checklist C: Ajuster un nœud batch axé efficacité

  1. Confirmez que la charge vise le débit et tolère le jitter.
  2. Activez/autorisez les C-states profonds du package et des gouverneurs équilibrés.
  3. Surveillez les tempêtes d’interruptions qui gardent le package réveillé (watts gaspillés).
  4. Mesurez l’énergie par job (ou par GB traité), pas seulement le temps d’exécution.

FAQ

1) Les P-states et C-states sont-ils indépendants ?

Majoritairement, mais pas totalement. Les C-states gèrent ce qui se passe quand on est inactif ; les P-states gèrent la performance en actif.
Dans la pratique, ils interagissent via la thermique et les limites de puissance : un sommeil profond peut améliorer la marge turbo, et désactiver l’inactivité peut réduire le boost soutenu.

2) Dois-je toujours utiliser le gouverneur performance sur les serveurs ?

Non. Pour les frontends critiques en latence, cela peut aider. Pour des flottes batch, c’est souvent un gaspillage.
De plus, sur intel_pstate, performance ne signifie pas « horloge maximale fixe ». Cela signifie une politique plus agressive.
Prenez des décisions par rôle et mesurez p99 et watts.

3) Si les C-states ajoutent de la latence, pourquoi ne pas les désactiver partout ?

Parce que vous paierez en consommation, chaleur et parfois throttling — plus une marge turbo réduite.
Désactiver les C-states profonds peut être un outil ciblé pour certains rôles. Ce n’est rarement un bon défaut pour toute une flotte.

4) Pourquoi la latence s’améliore-t-elle sous charge soutenue ?

Sous charge soutenue, les cœurs restent en C0 et les fréquences se stabilisent à des niveaux supérieurs. Vous évitez les coûts de réveil et de montée en fréquence.
Les charges en rafales paient ces coûts à répétition, et la latence de queue en souffre.

5) Comment savoir si l’OS ou le matériel contrôle la fréquence ?

Commencez par cpupower frequency-info pour voir le pilote. Sur les Intel modernes, intel_pstate en mode actif signifie que le matériel joue un grand rôle.
Regardez aussi si la fréquence courante est « asserted by call to hardware » dans la sortie.

6) La virtualisation change-t-elle l’histoire ?

Oui. L’état d’inactivité d’un invité est médié par l’hyperviseur. La fréquence et le sommeil package profond sont typiquement contrôlés par l’hôte.
Si vous réglez à l’intérieur d’une VM et ne voyez pas d’effet, ce n’est pas parce que vous êtes malchanceux ; c’est parce que vous ne manipulez pas les bons leviers.

7) Quelle est la différence opérationnelle entre C-states du cœur et du package ?

Les C-states du cœur affectent la profondeur de sommeil d’un seul cœur et la latence de réveil. Les états package affectent les composants au niveau du socket et peuvent économiser beaucoup plus d’énergie.
Les états package peuvent aussi entraîner des pénalités plus visibles sur la « première requête après l’inactivité », selon la plateforme.

8) L’ajustement des interruptions peut-il corriger la latence liée aux C-states ?

Parfois. Si des interruptions réveillent constamment des cœurs inactifs, vous verrez du gaspillage d’énergie et du jitter.
Si les interruptions sont concentrées sur quelques CPU, ces CPU peuvent ne jamais dormir tandis que d’autres plongent profondément, créant un comportement de réponse inégal.
Équilibrer ou épingler correctement les interruptions peut stabiliser la latence.

9) Comment choisir entre modes « faible latence » et « économe en énergie » ?

Utilisez le SLO de la charge et le modèle de coût. Si vous avez des cibles p99 strictes et du trafic en rafales, penchez vers faible latence sur ces nœuds.
Si vous avez des jobs batch ou des files élastiques, privilégiez l’efficacité. Évitez de mélanger des politiques dans le même pool derrière un load balancer.

10) Quelle est la première expérience sûre si je suspecte les C-states ?

Faites un canari sur un seul nœud : capturez la baseline, puis passez à un profil tuned faible latence ou limitez temporairement l’état d’inactivité le plus profond.
Si le p99 s’améliore sans déclencher de throttling ni d’augmentation inacceptable de la consommation, vous avez prouvé la causalité.

Conclusion : étapes pratiques suivantes

« Inactif » n’est pas un état neutre. C’est une décision active prise par des couches de silicium, firmware et code noyau — chacune tentant d’économiser de l’énergie,
et occasionnellement vous volant votre budget de latence au passage.

Étapes suivantes qui tiennent réellement en production :

  1. Mesurez avant d’ajuster : collectez pilote/gouverneur, résidence C-state et p95/p99 sur un nœud canari.
  2. Standardisez la politique firmware : des valeurs BIOS incohérentes tuent silencieusement une flotte.
  3. Séparez par rôle : nœuds faible-latence et nœuds efficacité ne doivent pas partager la même politique d’alimentation.
  4. Rendez les changements réversibles : bascules via profils tuned ou gestion de configuration, pas des SSH artisanaux.
  5. Surveillez la latence de queue et les retries : les moyennes vous mentiront avec un sérieux aplomb.

Cache CPU (L1/L2/L3) en clair : pourquoi la mémoire gagne

Votre service est « lié au CPU ». Les tableaux de bord le disent. Le CPU est à 80–90 %, la latence est mauvaise, et la première réaction de l’équipe est d’ajouter des cœurs.
Puis vous ajoutez des cœurs et rien ne s’améliore. Ou ça empire. Félicitations : vous venez de rencontrer le véritable boss — la mémoire.

Les caches CPU (L1/L2/L3) existent parce que les processeurs modernes peuvent faire des calculs plus vite que votre système ne peut leur fournir des données. La plupart des
défaillances de performances en production ne sont pas un « CPU lent ». Ce sont des « CPU qui attendent ». Cet article explique les caches sans langue de bois, puis montre
comment prouver ce qui se passe sur une machine Linux réelle avec des commandes que vous pouvez lancer aujourd’hui.

Pourquoi la mémoire gagne (et que le CPU attend surtout)

Les CPU sont absurdes. Un cœur moderne peut exécuter plusieurs instructions par cycle, spéculer, réordonner, vectoriser, et agir généralement comme un comptable surexcité faisant des
déclarations à 4 h du matin. Pendant ce temps, la DRAM est relativement lente. Le cœur peut clore des instructions en sous-nanosecondes ; un aller-retour vers la DRAM peut prendre
des dizaines à des centaines de nanosecondes selon la topologie, la contention et si vous êtes allé chercher de la mémoire NUMA distante.

Conséquence pratique : votre CPU passe beaucoup de temps bloqué sur des chargements mémoire. Pas sur le disque. Pas sur le réseau. Pas même sur du « code lent » au sens habituel.
Il attend la prochaine ligne de cache.

Les caches tentent de garder le CPU occupé en maintenant les données fréquemment utilisées à proximité. Ce n’est pas « agréable à avoir ». C’est la seule raison pour laquelle l’informatique
générale fonctionne aux fréquences actuelles. Si chaque chargement touchait la DRAM, vos cœurs passeraient la plupart des cycles à tourner en rond.

Voici le modèle mental qui résiste en production : la performance est dominée par la fréquence des ratés de cache et
le coût de ces ratés. Les ratés les plus coûteux sont ceux qui quittent le package du processeur et vont jusqu’à la DRAM, et les vraiment épicés sont les accès à la DRAM NUMA
distante via un interconnect pendant que d’autres cœurs se battent pour la bande passante.

Une règle empirique : quand votre chemin de requête touche « beaucoup de choses », le coût n’est pas l’arithmétique ; ce sont les suivis de pointeurs et les ratés de cache.
Et si vous le faites en concurrence avec de nombreux threads, vous pouvez transformer votre sous-système mémoire en goulot d’étranglement tandis que les graphes CPU vous mentent.

L1/L2/L3 en clair

Pensez aux niveaux de cache comme à des « garde-manger » de plus en plus grands et de plus en plus lents entre le cœur et la DRAM.
La dénomination est historique et simple : L1 est le plus proche du cœur, L2 vient ensuite, L3 est généralement partagé entre les cœurs sur un socket (pas toujours), puis vient la DRAM.

À quoi sert chaque niveau

  • Cache L1 : minuscule et extrêmement rapide. Souvent séparé en L1i (instructions) et L1d (données). C’est le premier endroit où le cœur regarde.
  • Cache L2 : plus grand, un peu plus lent, typiquement privé par cœur. Il récupère ce qui tombe hors du L1.
  • Cache L3 : beaucoup plus grand, plus lent, souvent partagé entre cœurs. Il réduit les allers-retours vers la DRAM et agit comme un amortisseur de contention.

Ce que signifient opérationnellement « hit » et « miss »

Un hit de cache signifie que les données dont vous avez besoin sont déjà à proximité ; le chargement est satisfait rapidement et le pipeline continue.
Un miss de cache signifie que le CPU doit aller chercher ces données à un niveau inférieur. Si le miss atteint la DRAM, le cœur peut être fortement bloqué.

Les misses surviennent parce que les caches sont finis et parce que les charges réelles ont des schémas d’accès désordonnés. Le CPU essaie de prédire et de précharger, mais il ne peut pas tout prévoir—
surtout le code axé sur les pointeurs, l’accès aléatoire ou les structures de données plus grandes que le cache.

Pourquoi vous ne pouvez pas « simplement utiliser le L3 »

On parle parfois du L3 comme d’un pool partagé magique qui retiendra votre working set. Ce n’est pas le cas. L3 est partagé, sujet à la contention, et souvent inclusif ou partiellement inclusif
selon l’architecture. De plus, la bande passante et la latence du L3 restent meilleures que la DRAM, mais elles ont un coût.

Si le working set de votre charge est plus grand que le L3, vous irez en DRAM. Si c’est plus grand que la DRAM… eh bien, ça s’appelle le « swap », et c’est un appel à l’aide.

Lignes de cache, localité et la règle « vous l’avez touché, vous l’avez acheté »

Les CPU ne chargent pas des octets uniques en cache. Ils chargent des lignes de cache, généralement 64 octets sur x86_64. Quand vous chargez une valeur, vous ramenez souvent
des valeurs voisines aussi. C’est bien si votre code utilise la mémoire voisine (localité spatiale). C’est mauvais si vous ne vouliez qu’un champ et que le reste est du junk,
car vous venez de polluer le cache avec des choses que vous ne réutiliserez pas.

La localité est tout le jeu :

  • Localité temporelle : si vous réutilisez bientôt, le cache aide.
  • Localité spatiale : si vous utilisez la mémoire voisine, le cache aide.

Les bases de données, caches et routeurs de requêtes vivent ou meurent souvent selon la prévisibilité de leurs schémas d’accès. Les scans séquentiels peuvent être rapides parce que
les préchargeurs matériels suivent. Le suivi de pointeurs aléatoire dans une grande table de hachage peut être lent parce que chaque étape signifie « surprise, aller en mémoire ».

Traduction opérationnelle : si vous voyez un CPU élevé mais aussi beaucoup de cycles bloqués, vous n’avez pas un problème de « calcul ». Vous avez un problème de « nourrir le cœur ».
Votre chemin critique est probablement dominé par des misses de cache ou des mauvaises prédictions de branche, pas par des calculs.

Blague n°1 : les misses de cache sont comme les « questions rapides » dans le chat d’entreprise — chacune semble petite jusqu’à ce que vous réalisiez que votre journée entière attend sur elles.

Préchargement : la tentative utile du CPU

Les CPU essaient de détecter des motifs et de précharger les lignes de cache futures. Ça marche bien pour les accès en streaming et striés. Ça marche mal pour le suivi de pointeurs,
parce que l’adresse du prochain chargement dépend du résultat du précédent.

C’est pourquoi « j’ai optimisé la boucle » ne change parfois rien. La boucle n’est pas le problème ; c’est la chaîne de dépendances mémoire.

La partie que personne ne veut déboguer : cohérence et faux partage

Dans les systèmes multi‑cœurs, chaque cœur a ses propres caches. Quand un cœur écrit une ligne de cache, les copies des autres cœurs doivent être invalidées ou mises à jour pour que tout le monde
voie une vue cohérente. C’est la cohérence de cache. C’est nécessaire. C’est aussi un piège de performance.

Faux partage : quand vos threads se disputent une ligne de cache qu’ils ne « partagent » pas

Le faux partage survient lorsque deux threads mettent à jour des variables différentes qui se trouvent sur la même ligne de cache. Ils ne partagent pas logiquement les données, mais le protocole de
cohérence considère la ligne entière comme une unité. Ainsi chaque écriture déclenche des invalidations et des transferts de propriété, et vos performances plongent.

Au niveau des symptômes, cela ressemble à « plus de threads a ralenti » avec beaucoup de temps CPU dépensé, mais peu de progrès. Vous verrez beaucoup de trafic cache-à-cache et des misses de cohérence
si vous regardez avec les bons outils.

Blague n°2 : le faux partage c’est quand deux équipes « possèdent » la même cellule de feuille de calcul ; les modifications sont correctes, le processus ne l’est pas.

Les charges d’écriture paient plus cher

Les lectures peuvent être partagées. Les écritures nécessitent la propriété exclusive de la ligne, ce qui déclenche des actions de cohérence. Si vous avez un compteur chaud mis à jour par de nombreux threads,
le compteur devient un goulot sérialisé même si vous « avez beaucoup de cœurs ».

C’est pourquoi existent les compteurs par thread, les verrous éclatés et le batching. Vous n’êtes pas en train d’être fantaisiste. Vous évitez une facture physique.

NUMA : la taxe de latence que vous payez en montée en charge

Sur beaucoup de serveurs, la mémoire est physiquement attachée aux sockets CPU. Accéder à la mémoire « locale » est plus rapide qu’accéder à la mémoire attachée à un autre socket.
C’est NUMA (Non-Uniform Memory Access). Ce n’est pas un cas marginal. C’est le réglage par défaut sur beaucoup de matériel de production.

Vous pouvez vous permettre d’ignorer NUMA jusqu’à ce que vous ne le puissiez plus. Le mode d’échec apparait quand :

  • vous répartissez les threads sur plusieurs sockets,
  • votre allocateur étale les pages entre les nœuds,
  • ou votre ordonnanceur migre des threads loin de leur mémoire.

Ensuite, la latence explose, le débit plafonne et le CPU semble « occupé » parce qu’il est bloqué. Vous pouvez facilement perdre des semaines à optimiser le code applicatif alors que la correction
consiste à pinner les processus, corriger la politique d’allocation, ou choisir moins de sockets avec des horloges plus élevées pour les charges sensibles à la latence.

Faits intéressants et histoire à répéter en réunion

  1. Le « mur de la mémoire » est devenu une préoccupation grand public dans les années 1990 : la vitesse des CPU a augmenté plus vite que la latence DRAM, rendant les caches indispensables.
  2. Les tailles de ligne de cache sont un choix de conception : 64 octets est courant sur x86, mais d’autres architectures ont utilisé des tailles différentes ; c’est un compromis entre bande passante et pollution.
  3. Le L1 est souvent séparé en caches d’instructions et de données parce que les mélanger provoque des conflits ; les flux de code et de données ont des motifs différents.
  4. Le partage du L3 est intentionnel : il aide quand des threads partagent des données majoritairement en lecture et réduit les allers-retours DRAM, mais crée aussi de la contention sous charge.
  5. Les préchargeurs matériels existent parce que l’accès séquentiel est fréquent ; ils peuvent accélérer considérablement les lectures en streaming sans changement de code.
  6. Les protocoles de cohérence (variantes de MESI) sont une grande raison pour laquelle le multi‑cœur « fonctionne simplement », mais ils imposent aussi des coûts réels sous contention d’écriture.
  7. Les TLB sont aussi des caches : la Translation Lookaside Buffer met en cache les traductions d’adresse ; les misses de TLB peuvent faire aussi mal que les misses de cache.
  8. Les huge pages réduisent la pression sur le TLB en mappant plus de mémoire par entrée ; elles peuvent aider certaines charges et nuire à d’autres.
  9. Les surprises de montée en charge multi‑cœur des années 2000 ont appris aux équipes que « plus de threads » n’est pas un plan de performance si la mémoire et le verrouillage ne sont pas gérés.

Playbook de diagnostic rapide

Quand un système est lent, vous voulez trouver la ressource limitante rapidement, pas écrire de la poésie sur la microarchitecture. Voici une checklist de terrain.

Première étape : confirmer si vous êtes lié au calcul ou bloqué

  • Vérifiez l’utilisation CPU et les métriques liées au niveau d’exécution : file d’attente run, changements de contexte, pression IRQ.
  • Cherchez les cycles bloqués / les misses de cache avec perf si possible.
  • Si les instructions par cycle sont faibles et que les misses de cache sont élevés, c’est probablement lié à la latence mémoire ou à la bande passante mémoire.

Deuxième étape : décider si c’est lié à la latence ou à la bande passante

  • Latence : suivi de pointeurs, accès aléatoire, beaucoup de misses LLC, faible bande passante mémoire.
  • Bande passante : streaming, grands scans, nombreux cœurs lisant/écrivant, bande passante mémoire proche des limites de la plateforme.

Troisième étape : vérifier NUMA et la topologie

  • Les threads tournent‑ils sur un socket mais allouent sur un autre ?
  • Faites‑vous du thrashing inter-socket du LLC ?
  • La charge est‑elle sensible à la latence tail (elle l’est généralement), faisant de la mémoire distante un tueur silencieux ?

Quatrième étape : vérifier l’« évident mais ennuyeux »

  • Y a‑t‑il du swap ou de la pression mémoire (storms de reclaim) ?
  • Atteignez‑vous des limites mémoire de cgroup ?
  • Saturez‑vous un verrou ou un compteur unique (faux partage, mutex contendu) ?

Idée paraphrasée (attribuée) : le message opérationnel de Gene Kim est que les boucles de rétroaction rapides battent l’héroïsme — mesurer d’abord, puis changer une chose à la fois.

Tâches pratiques : commandes, sorties et décisions

Celles‑ci sont destinées à être exécutées sur un hôte Linux où vous diagnostiquez des performances. Certaines nécessitent les droits root ou des permissions perf.
L’objectif n’est pas de mémoriser les commandes ; c’est de relier les sorties aux décisions.

Task 1: Identify cache sizes and topology

cr0x@server:~$ lscpu
Architecture:             x86_64
CPU(s):                   64
Thread(s) per core:       2
Core(s) per socket:       16
Socket(s):                2
L1d cache:                32K
L1i cache:                32K
L2 cache:                 1M
L3 cache:                 35.8M
NUMA node(s):             2
NUMA node0 CPU(s):        0-31
NUMA node1 CPU(s):        32-63

Ce que cela signifie : vous avez deux sockets, deux nœuds NUMA, et un L3 par socket (souvent). Votre working set qui déborde d’environ 36 Mo par socket
commence à payer le prix DRAM.
Décision : si le service est sensible à la latence, prévoyez une prise en compte de NUMA (pinning, politique mémoire) et gardez les structures de données chaudes petites.

Task 2: Verify cache line size (and stop guessing)

cr0x@server:~$ getconf LEVEL1_DCACHE_LINESIZE
64

Ce que cela signifie : les frontières de risque de faux partage sont de 64 octets.
Décision : dans le code bas niveau, alignez les compteurs/structs chauds par thread sur des frontières de 64B pour éviter le ping‑pong des lignes de cache.

Task 3: Confirm NUMA distances

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 20 21 22 23 24 25 26 27 28 29 30 31
node 0 size: 256000 MB
node 0 free: 120000 MB
node 1 cpus: 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
node 1 size: 256000 MB
node 1 free: 118000 MB
node distances:
node   0   1
  0:  10  21
  1:  21  10

Ce que cela signifie : la mémoire distante a une « distance » d’environ 2x. Pas littéralement 2x la latence, mais directionnellement significatif.
Décision : si vous êtes sensible à la latence tail, gardez les threads et leur mémoire locaux (ou réduisez le trafic inter-socket en limitant l’affinité CPU).

Task 4: Check if the kernel is fighting you with automatic NUMA balancing

cr0x@server:~$ cat /proc/sys/kernel/numa_balancing
1

Ce que cela signifie : le noyau peut migrer des pages pour « suivre » les threads. Parfois utile, parfois bruyant.
Décision : pour des charges stables et pinées, vous pouvez le désactiver (avec prudence, testé) ou imposer un placement explicite.

Task 5: Observe per-process NUMA memory placement

cr0x@server:~$ pidof myservice
24718
cr0x@server:~$ numastat -p 24718
Per-node process memory usage (in MBs) for PID 24718 (myservice)
Node 0          38000.25
Node 1           2100.10
Total           40100.35

Ce que cela signifie : le processus utilise principalement la mémoire du nœud 0. Si ses threads tournent sur le nœud 1, vous paierez des pénalités distantes.
Décision : alignez l’affinité CPU et la politique d’allocation mémoire ; si la répartition est accidentelle, corrigez l’ordonnancement ou le placement au démarrage.

Task 6: Check memory pressure and swapping (the performance cliff)

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
 3  0      0 1200000  80000 9000000   0    0     2    15  900 3200 45  7 48  0  0
 5  0      0 1180000  80000 8900000   0    0     0     0 1100 4100 55  8 37  0  0
 7  0      0 1170000  80000 8850000   0    0     0     0 1300 5200 61  9 30  0  0

Ce que cela signifie : pas de swap‑in/out (si/so = 0), donc vous n’êtes pas dans la catégorie « tout est terrible ». Le CPU est occupé, mais pas en attente d’IO.
Décision : passez à l’analyse cache/mémoire ; ne perdez pas de temps à accuser le disque.

Task 7: See if you’re bandwidth-bound (quick read on memory throughput)

cr0x@server:~$ sudo perf stat -a -e cycles,instructions,cache-references,cache-misses,LLC-loads,LLC-load-misses -I 1000 -- sleep 5
# time(ms)  cycles        instructions   cache-references  cache-misses  LLC-loads    LLC-load-misses
     1000   5,210,000,000  2,340,000,000  120,000,000       9,800,000     22,000,000   6,700,000
     2000   5,300,000,000  2,310,000,000  118,000,000      10,200,000     21,500,000   6,900,000
     3000   5,280,000,000  2,290,000,000  121,000,000      10,500,000     22,300,000   7,100,000

Ce que cela signifie : instructions/cycle est relativement bas (environ 0,43 ici), et les misses de cache/LLC sont significatifs. Le CPU attend beaucoup.
Décision : traitez cela comme dominé par la latence mémoire à moins que les compteurs de bande passante n’indiquent une saturation ; cherchez des accès aléatoires, du pointer chasing ou NUMA.

Task 8: Identify top functions and whether they stall (profile with perf)

cr0x@server:~$ sudo perf top -p 24718
Samples: 2K of event 'cycles', Event count (approx.): 2500000000
  18.50%  myservice  myservice  [.] hashmap_lookup
  12.20%  myservice  myservice  [.] parse_request
   8.90%  libc.so.6  libc.so.6   [.] memcmp
   7.40%  myservice  myservice  [.] cache_get
   5.10%  myservice  myservice  [.] serialize_response

Ce que cela signifie : les hotspots sont des recherches/comparaisons — candidats classiques pour des misses de cache et des mauvaises prédictions de branche.
Décision : inspectez les structures de données : les clés sont‑elles dispersées ? poursuivez‑vous des pointeurs ? pouvez‑vous compacter les données ? réduire les comparaisons ?

Task 9: Check for scheduler migration (NUMA’s quiet enabler)

cr0x@server:~$ pidstat -w -p 24718 1 3
Linux 6.5.0 (server)  01/09/2026  _x86_64_  (64 CPU)

01:02:11      UID       PID   cswch/s nvcswch/s  Command
01:02:12     1001     24718   1200.00    850.00  myservice
01:02:13     1001     24718   1350.00    920.00  myservice
01:02:14     1001     24718   1100.00    800.00  myservice

Ce que cela signifie : des changements de contexte élevés peuvent indiquer une contention sur des verrous ou trop de threads exécutables.
Décision : si la latence est en pics, réduisez le nombre de threads, enquêtez sur les verrous, ou pinnez les threads critiques pour réduire la migration.

Task 10: Check run queue and per-CPU saturation (don’t confuse “busy” with “progress”)

cr0x@server:~$ mpstat -P ALL 1 2
Linux 6.5.0 (server)  01/09/2026  _x86_64_  (64 CPU)

01:03:01 AM  CPU   %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
01:03:02 AM  all   62.0  0.0   9.0   0.1    0.0  0.5    0.0    0.0    0.0   28.4
01:03:02 AM   0    95.0  0.0   4.0   0.0    0.0  0.0    0.0    0.0    0.0    1.0
01:03:02 AM  32    20.0  0.0   5.0   0.0    0.0  0.0    0.0    0.0    0.0   75.0

Ce que cela signifie : le CPU0 est saturé tandis que le CPU32 est plutôt inactif. Cela peut être un problème d’affinité, un shard chaud unique, ou un goulet de verrou.
Décision : si un seul cœur est chaud, la montée en charge ne fonctionnera pas tant que vous n’avez pas supprimé le funnel. Inspectez la distribution du travail par cœur et les verrous.

Task 11: Verify CPU affinity and cgroup constraints

cr0x@server:~$ taskset -pc 24718
pid 24718's current affinity list: 0-15

Ce que cela signifie : le processus est épinglé aux CPU 0–15 (un sous-ensemble d’un socket). Cela peut être volontaire ou accidentel.
Décision : si épinglé, assurez‑vous que la mémoire est locale à ce nœud ; si c’est accidentel, corrigez votre unité/systemd ou le CPU set de l’orchestrateur.

Task 12: Check LLC miss rate per process (perf stat on PID)

cr0x@server:~$ sudo perf stat -p 24718 -e cycles,instructions,LLC-loads,LLC-load-misses -- sleep 10
 Performance counter stats for process id '24718':

     18,320,000,000      cycles
      7,410,000,000      instructions              #    0.40  insn per cycle
        210,000,000      LLC-loads
         78,000,000      LLC-load-misses           #   37.14% of all LLC hits

      10.001948393 seconds time elapsed

Ce que cela signifie : un taux de miss LLC d’environ 37 % est un signal fort que votre working set ne tient pas dans le cache ou que l’accès est aléatoire.
Décision : réduisez le working set, augmentez la localité, ou changez la disposition des données. Validez aussi la localité NUMA.

Task 13: Spot page faults and major faults (TLB and paging hints)

cr0x@server:~$ pidstat -r -p 24718 1 3
Linux 6.5.0 (server)  01/09/2026  _x86_64_  (64 CPU)

01:04:10      UID       PID  minflt/s  majflt/s     VSZ     RSS  %MEM  Command
01:04:11     1001     24718   8200.00      0.00  9800000 4200000  12.8  myservice
01:04:12     1001     24718   7900.00      0.00  9800000 4200000  12.8  myservice
01:04:13     1001     24718   8100.00      0.00  9800000 4200000  12.8  myservice

Ce que cela signifie : des faults mineurs élevés peuvent être normaux (paging à la demande, fichiers mmappés), mais si les faults montent sous charge cela peut être corrélé au churn de pages et à la pression sur le TLB.
Décision : si les faults se corrèlent avec des pics de latence, vérifiez le comportement de l’allocateur, l’usage de mmap, et envisagez les huge pages seulement après mesure.

Task 14: Validate transparent huge pages (THP) status

cr0x@server:~$ cat /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never

Ce que cela signifie : THP est toujours activé. Certaines bases de données l’adorent, certains services sensibles à la latence détestent le comportement d’allocation/compaction.
Décision : si vous observez des stalls périodiques, testez madvise ou never en staging et comparez la latence tail.

Task 15: Check memory bandwidth counters (Intel/AMD tooling varies)

cr0x@server:~$ sudo perf stat -a -e uncore_imc_0/cas_count_read/,uncore_imc_0/cas_count_write/ -- sleep 5
 Performance counter stats for 'system wide':

       8,120,000,000      uncore_imc_0/cas_count_read/
       4,010,000,000      uncore_imc_0/cas_count_write/

       5.001234567 seconds time elapsed

Ce que cela signifie : ces compteurs approximant les transactions DRAM ; si elles sont élevées et proches des limites de la plateforme, vous êtes lié à la bande passante.
Décision : si vous êtes limité par la bande passante, ajouter des cœurs n’aidera pas. Réduisez les données scannées, compressez, améliorez la localité ou rapprochez le travail des données.

Task 16: Identify lock contention (often misdiagnosed as “cache issues”)

cr0x@server:~$ sudo perf lock report -p 24718
Name                 acquired  contended   total wait (ns)   avg wait (ns)
pthread_mutex_lock      12000       3400      9800000000         2882352

Ce que cela signifie : les threads passent du temps réel à attendre des verrous. Cela peut amplifier les effets de cache (les lignes de cache rebondissent avec la propriété du verrou).
Décision : réduisez la granularité des verrous, éclatez-les, ou changez l’algorithme. N’« optimisez pas la mémoire » si votre goulot est un mutex.

Task 17: Watch LLC occupancy and memory stalls (if supported)

cr0x@server:~$ sudo perf stat -p 24718 -e cpu/mem-loads/,cpu/mem-stores/ -- sleep 5
 Performance counter stats for process id '24718':

        320,000,000      cpu/mem-loads/
         95,000,000      cpu/mem-stores/

       5.000912345 seconds time elapsed

Ce que cela signifie : un trafic lourd de loads/stores suggère que le travail est centré sur la mémoire. Combinez avec les métriques de miss LLC pour décider si c’est cache-friendly.
Décision : si les loads sont nombreux avec des taux de miss élevés, concentrez‑vous sur la localité des structures et la réduction du pointer chasing.

Task 18: Validate that you’re not accidentally throttling (frequency matters)

cr0x@server:~$ cat /proc/cpuinfo | grep -m1 "cpu MHz"
cpu MHz		: 1796.234

Ce que cela signifie : la fréquence CPU est relativement basse (possiblement économie d’énergie ou contraintes thermiques).
Décision : si la performance a régressé après un changement de plateforme, validez le governor CPU et les thermiques avant d’accuser les caches.

Trois mini-récits d’entreprise issus du terrain

Mini‑récit 1 : L’incident causé par une fausse hypothèse

Un service de paiements a commencé à générer des timeouts chaque jour à peu près à la même heure. L’équipe l’a appelé « saturation CPU » parce que les tableaux de bord montraient le CPU à 90 %,
et le flame graph mettait en avant le parsing JSON et du hashing. Ils ont fait ce que font les équipes : ajouté des instances, augmenté les pools de threads, relevé les limites d’autoscaling.
L’incident a empiré. Les queues de latence se sont allongées.

La fausse hypothèse était subtile : « CPU élevé signifie cœur occupé à calculer ». En réalité, les cœurs attendaient. perf stat montrait un IPC bas et un fort taux de miss LLC.
Le chemin de requête avait une recherche « d’enrichissement » cachée qui avait silencieusement grossi : plus de clés, plus de métadonnées, plus d’objets pointer-heavy, et un working set qui ne tenait plus près du L3.

Ensuite, le changement de scaling l’a poussé dans un nouveau mode d’échec. Plus de threads signifiait plus d’accès aléatoires en parallèle, ce qui a augmenté le parallélisme mémoire
mais aussi la contention. Le contrôleur mémoire a chauffé, la bande passante est montée, et la latence moyenne a suivi. Classique : plus vous poussez, plus le sous-système mémoire résiste.

La correction n’a pas été héroïque. Ils ont réduit l’overhead des objets, compacté les champs dans des tableaux contigus pour le hot path, et plafonné l’ensemble d’enrichissement par requête.
Ils ont aussi arrêté d’épingler le processus sur les deux sockets sans contrôler le placement mémoire. Une fois la localité améliorée, l’utilisation CPU est restée élevée,
mais le débit a augmenté et la latence tail a chuté. Les graphes CPU ressemblaient aux mêmes ; le comportement système était différent. Voilà la leçon.

Mini‑récit 2 : L’optimisation qui a échoué

Une équipe a voulu accélérer une API d’analytics en « améliorant le cache ». Ils ont remplacé un simple vecteur de structs par une table de hachage mappée par chaîne pour éviter les scans linéaires.
Les microbenchmarks sur un laptop semblaient excellents. La production a dit non.

La nouvelle structure a détruit la localité. L’ancien code scannait un tableau contigu : prédictible, favorable au prefetch, ami du cache. Le nouveau code faisait des recherches aléatoires,
chacune impliquant du pointer chasing, du hachage de chaînes et plusieurs chargements dépendants. Sur de vrais serveurs sous charge, cela a transformé une boucle majoritairement L2/L3-friendly
en une fête DRAM.

Pire, la table de hachage a introduit un chemin de redimensionnement partagé. Sous rafales, des redimensionnements ont eu lieu, des verrous ont été disputés, et les lignes de cache ont rebondi entre cœurs.
L’équipe a vu un CPU plus élevé et a conclu « il nous faut plus de CPU ». Mais le « plus de CPU » a augmenté la contention, et leur p99 s’est dégradé.

Ils ont roll-back, puis implémenté un compromis ennuyeux : garder un vecteur trié pour le hot path et faire des rebuilds occasionnels hors du thread de requête, avec un pointeur snapshot stable.
Ils ont accepté O(log n) avec bonne localité au lieu de O(1) avec des constantes terribles. La production est redevenue ennuyeuse, ce qui est le type de succès sur lequel on peut construire une carrière.

Mini‑récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la journée

Un service adjacent au stockage — beaucoup de lectures de métadonnées, quelques écritures — a été migré vers une nouvelle plateforme matérielle. Tout le monde s’attendait à une amélioration.
Ce ne fut pas le cas. Il y avait des pics de latence sporadiques et des baisses de débit occasionnelles, sans rien d’évident : pas de swap, disques OK, réseau OK.

L’équipe avait une habitude qui les a sauvés : un « bundle de triage performance » qu’ils exécutent pour toute régression. Il incluait lscpu,
topologie NUMA, perf stat pour IPC et misses LLC, et un contrôle rapide de la fréquence CPU et des governors. Pas excitant. Fiable.

Le bundle a immédiatement montré deux surprises. Premièrement, les nouveaux hôtes avaient plus de sockets, et le service était ordonnancé à travers les sockets sans placement mémoire cohérent.
Deuxièmement, la fréquence CPU était plus basse sous charge soutenue à cause des réglages d’énergie de l’image de base.

La correction a été procédurale : ils ont mis à jour le baseline de tuning hôte (governor, réglages firmware quand pertinent), et ils ont épinglé le service à un seul nœud NUMA avec mémoire liée à ce nœud.
Aucun changement de code. La latence s’est stabilisée. Le déploiement s’est terminé. Le postmortem a été court, ce qui est un luxe.

Erreurs courantes (symptômes → cause racine → correction)

1) « Le CPU est élevé donc il nous faut plus de CPU »

Symptômes : CPU 80–95 %, débit stable, p95/p99 empirent en ajoutant threads/instances.
Cause racine : IPC faible dû aux misses de cache ou stalls mémoire ; le CPU est « occupé à attendre ».
Correction : mesurer IPC et misses LLC avec perf stat ; réduire le working set, améliorer la localité, ou corriger le placement NUMA. Ne pas augmenter les threads aveuglément.

2) « Une table de hachage est toujours plus rapide qu’un scan »

Symptômes : plus lent après passage à une structure « O(1) » ; perf montre des hotspots en hashing/strcmp/memcmp.
Cause racine : accès aléatoire et pointer chasing provoquent des allers-retours DRAM ; la mauvaise localité bat le grand‑O sur du matériel réel.
Correction : privilégier des structures contiguës pour les hot paths (tableaux, vecteurs, vecteurs triés). Benchmarkez avec des jeux de données et de la concurrence proches de la production.

3) « Plus de threads = plus de débit »

Symptômes : le débit s’améliore puis s’effondre ; les changements de contexte augmentent ; les misses LLC grimpent.
Cause racine : saturation de la bande passante mémoire, contention de verrous, ou faux partage domine.
Correction : plafonnez le nombre de threads près du « knee » de la courbe ; éclatez les verrous/compteurs ; évitez les écritures partagées chaudes ; épinglez les threads si sensible à NUMA.

4) « NUMA n’a pas d’importance ; Linux gère »

Symptômes : bonne latence moyenne, latence tail catastrophique ; régressions en changeant vers des hôtes multi-socket.
Cause racine : accès mémoire distant et trafic inter-socket ; la migration de l’ordonnanceur casse la localité.
Correction : utilisez numastat et numactl ; épinglez CPU et mémoire ; envisagez d’exécuter un processus par socket pour la prévisibilité.

5) « Si on désactive les caches, on teste le pire cas »

Symptômes : quelqu’un propose de désactiver les caches ou de vider constamment comme stratégie de test.
Cause racine : incompréhension ; les systèmes modernes ne sont pas conçus pour ce mode et les résultats ne se transposeront pas à la réalité.
Correction : testez avec des working sets réalistes et des schémas d’accès ; utilisez les compteurs de profiling, pas des expériences de foire scientifique.

6) « Les huge pages aident toujours »

Symptômes : THP activé et stalls périodiques ; activité de compaction ; pics de latence pendant la croissance mémoire.
Cause racine : overhead d’allocation/compaction THP ; inadéquation avec les schémas d’allocation.
Correction : benchmarquez always vs madvise vs never ; si vous utilisez des huge pages, allouez‑les à l’avance et surveillez la latence tail.

Listes de contrôle / plan pas à pas

Checklist A: Prouver que c’est la mémoire, pas le calcul

  1. Capturez la topologie CPU : lscpu. Enregistrez sockets/NUMA et tailles de cache.
  2. Vérifiez le swap/la pression mémoire : vmstat 1. Si si/so > 0, réglez la mémoire d’abord.
  3. Mesurez IPC et misses LLC : perf stat (système ou PID). IPC bas + misses LLC élevés = suspicion de stall mémoire.
  4. Cherchez les fonctions chaudes : perf top. Si les hotspots sont lookup/compare/alloc, attendez des problèmes de localité.

Checklist B: Décider si c’est latence ou bande passante

  1. Si le taux de miss LLC est élevé mais que les compteurs de bande passante sont modérés : le pointer chasing lié à la latence est probable.
  2. Si les compteurs de bande passante sont proches des limites de la plateforme et que les cœurs n’aident pas : scan/stream lié à la bande passante est probable.
  3. Changez une chose et re‑mesurez : réduisez la concurrence, réduisez le working set, ou changez le schéma d’accès.

Checklist C: Corriger NUMA avant de réécrire le code

  1. Mappez les nœuds NUMA : numactl --hardware.
  2. Vérifiez la mémoire du processus par nœud : numastat -p PID.
  3. Vérifiez l’affinité CPU : taskset -pc PID.
  4. Alignez : épinglez les CPUs à un nœud et liez la mémoire au même nœud (testez en staging d’abord).

Checklist D: Rendre les données cache‑friendly (l’ennuyeux gagne)

  1. Aplatissez les structures pointer‑heavy dans les hot paths.
  2. Compressez les champs chauds ensemble ; séparez les champs froids (split hot/cold).
  3. Privilégiez tableaux/vecteurs et itération prédictible plutôt que l’accès aléatoire.
  4. Éclatez les compteurs write‑heavy ; regroupez les mises à jour.
  5. Benchmarkez avec des tailles proches de la production ; les effets cache apparaissent quand les données sont assez grandes pour importer.

Foire aux questions

1) Le L1 est‑il toujours plus rapide que le L2, et le L2 toujours plus rapide que le L3 ?

Généralement oui en termes de latence, mais la performance réelle dépend de la contention, du motif d’accès, et de si la ligne est déjà présente grâce au préchargement.
De plus, les caractéristiques de bande passante diffèrent ; le L3 peut offrir une bande passante agrégée élevée mais une latence supérieure.

2) Pourquoi mon CPU affiche 90 % d’utilisation s’il « attend la mémoire » ?

Parce que « utilisation CPU » signifie surtout que le cœur n’est pas inactif. Un pipeline bloqué exécute encore des instructions, gère des misses, fait de la spéculation,
et brûle des cycles. Vous avez besoin de compteurs (IPC, misses, cycles bloqués) pour voir l’attente.

3) Quelle est la différence entre le cache CPU et le cache de pages Linux ?

Les caches CPU sont gérés par le matériel et sont petits (Ko/Mo). Le cache de pages Linux est géré par l’OS, utilise la DRAM, et met en cache des données de fichiers (Go).
Ils interagissent, mais résolvent des problèmes à des échelles différentes.

4) Puis‑je « augmenter le L3 » par le logiciel ?

Pas littéralement. Ce que vous pouvez faire, c’est agir comme si vous aviez plus de cache en réduisant votre working set chaud, en améliorant la localité, et en évitant la pollution du cache.

5) Pourquoi les listes chaînées et les arbres pointer‑heavy sont‑ils mauvais ?

Ils détruisent la localité spatiale. Chaque pointeur mène à une ligne de cache différente, souvent loin. Cela signifie des chargements dépendants et des allers‑retours fréquents vers la DRAM,
qui bloquent le cœur.

6) Quand devrais‑je me soucier du faux partage ?

Quand plusieurs threads mettent à jour des champs/counters distincts dans des boucles serrées et que les performances empirent avec plus de threads.
C’est courant dans les compteurs métriques, les buffers circulaires et les « tableaux d’état par connexion » naïfs.

7) Les misses de cache sont‑ils toujours mauvais ?

Certains misses sont inévitables. La question est de savoir si votre charge est structurée pour amortir les misses (streaming) ou si elles sont catastrophiques (charges dépendantes aléatoires).
Vous optimisez pour réduire les misses sur le hot path, pas pour atteindre un mythique « zéro miss ».

8) Des CPUs plus rapides résolvent‑ils les problèmes mémoire ?

Parfois ils les aggravent. Des cœurs plus rapides peuvent exiger des données plus vite et heurter le mur mémoire plus rapidement. Une plateforme avec meilleure bande passante mémoire,
meilleure topologie NUMA ou caches plus larges compte souvent plus que le simple GHz.

9) Dois‑je tout épingler sur un socket ?

Pour les services sensibles à la latence, épingler sur un socket (et binder la mémoire) peut être un gros gain : localité prévisible, moins d’accès distants.
Pour les jobs à haut débit, répartir sur les sockets peut aider — si vous maintenez la localité et évitez les hotspots d’écriture partagés.

10) Quelle métrique surveiller sur les dashboards pour détecter tôt les problèmes de cache ?

Si possible, exportez IPC (instructions par cycle) et taux de miss LLC ou cycles bloqués depuis perf/PMU. Sinon, surveillez le motif :
CPU monte, débit stable, latence augmente lors du scaling. Ce motif crie « mémoire ».

Conclusion : que faire la semaine prochaine

Les caches CPU ne sont pas anecdotiques. Ce sont la raison pour laquelle un changement « simple » peut casser le p99 et pourquoi ajouter des cœurs ajoute souvent de la déception.
La mémoire gagne parce qu’elle fixe le rythme : si votre cœur ne peut pas obtenir les données bon marché, il ne peut pas faire du travail utile.

Prochaines étapes pratiques :

  • Ajoutez perf stat (IPC + misses LLC) à votre trousse d’incident standard pour les pages « liées CPU ».
  • Documentez la topologie NUMA par classe d’hôte et décidez si les services doivent être pinés (et comment) par défaut.
  • Auditez les hot paths pour la localité : aplatissez les structures, séparez hot/cold fields, et évitez les hotspots d’écritures partagées.
  • Benchmarkez avec des tailles de jeu de données réalistes. Si votre benchmark tient dans le L3, ce n’est pas un benchmark ; c’est une démo.
  • Quand on propose une optimisation, posez d’abord une question : « Qu’est‑ce que ça fait aux misses de cache et au trafic mémoire ? »

MySQL vs PostgreSQL: charges JSON — raccourci rapide ou douleur à long terme

Vous ajoutez une colonne JSON parce que vous « avez juste besoin de flexibilité ». Puis vos tableaux de bord ralentissent, vos réplicas prennent du retard,
et quelqu’un demande « une requête ad hoc rapide » qui se transforme en balayage de table sur des millions de lignes. JSON est le ruban adhésif de la modélisation des données :
parfois il sauve la journée, parfois c’est la raison pour laquelle la journée a dû être sauvée.

MySQL et PostgreSQL prennent tous deux en charge JSON, mais ils encouragent des habitudes très différentes. L’un vous permettra d’expédier rapidement et
d’accumuler discrètement de la dette. L’autre vous permettra de construire des index et des contraintes puissants — tout en vous donnant
assez de marge pour tisser un pull d’encombrement et de contention de verrous si vous êtes négligent.

La décision en une page : quoi choisir et quand

Utilisez PostgreSQL quand…

  • Vous avez besoin de requêtes riches (contenance, existence, filtres imbriqués) et voulez que l’optimiseur ait des options. JSONB + GIN de PostgreSQL est l’outil mature.
  • Vous voulez des contraintes autour de données semi-structurées : CHECK, index d’expression, colonnes générées et index fonctionnels sont des citoyens de première classe.
  • Vous vous attendez à ce que le JSON reste plus d’un trimestre. PostgreSQL a tendance à mieux vieillir quand JSON devient « schéma central ».
  • Vous savez gérer vacuum correctement. PostgreSQL vous récompensera, mais seulement si vous respectez l’entretien MVCC.

Utilisez MySQL quand…

  • Votre usage de JSON est principalement stockage de document + récupération, pas des filtrages analytiques lourds. Si les requêtes sont « récupérer par id, retourner le blob », MySQL peut très bien faire l’affaire.
  • Vous comptez sur les colonnes générées pour projeter des chemins JSON chauds en scalaires indexés. C’est la voie pratique de MySQL vers la prévisibilité.
  • Vous êtes déjà standardisé sur MySQL en exploitation et JSON représente un petit coin de la charge. Des opérations cohérentes valent mieux que l’élégance théorique.

Ce que je dirais à une équipe de production

Si vos colonnes JSON sont un hack transitoire (ingérer vite, normaliser plus tard), choisissez la base que votre équipe sait déjà
exploiter. Mais si JSON est le contrat d’interface (événements, configurations, feature flags, attributs utilisateur) et que vous prévoyez d’interroger
son contenu à l’échelle, PostgreSQL est généralement le pari à long terme le plus sûr.

MySQL peut bien performer avec JSON, mais il exige souvent que vous « déclariez les éléments importants » via des colonnes générées et des index ciblés.
Sinon, vous expliquerez à la direction comment votre schéma flexible est devenu une latence inflexible.

Une citation qui devrait figurer dans chaque runbook d’astreinte : « L’espoir n’est pas une stratégie. » — maxime opérationnelle largement répétée (idée paraphrasée).
Avec JSON, espérer que la base de données « trouve une solution » est la façon la plus sûre de s’offrir un incident le week-end.

Faits et historique : comment en sommes-nous arrivés là

JSON en base de données semble moderne, mais l’industrie tourne autour de cette idée depuis des décennies : « stocker des données flexibles près des données structurées,
et les interroger sans renoncer à la sécurité transactionnelle. » Les détails diffèrent, et ce sont ces détails qui expliquent pourquoi vous lisez ceci au lieu de dormir.

8 faits à garder en tête

  1. PostgreSQL a ajouté JSON en 9.2 (2012), puis a introduit JSONB en 9.4 (2014) pour un stockage binaire et une meilleure indexation.
  2. MySQL a introduit un type JSON natif en 5.7 (2015) ; avant cela, c’était du TEXT avec une prière et une regex.
  3. JSONB normalise l’ordre des clés et supprime les clés dupliquées (la dernière clé l’emporte). C’est excellent pour l’indexation, surprenant si vous voulez « stocker exactement ce que j’ai envoyé ».
  4. MySQL stocke aussi JSON dans un format binaire et valide le JSON à l’insertion, évitant certains cauchemars de « blob invalide ».
  5. Les index GIN de PostgreSQL ont été initialement conçus pour la recherche textuelle, puis sont devenus l’outil de travail pour la contenance JSONB.
  6. Les colonnes générées de MySQL existent depuis 5.7, et elles expliquent pourquoi de nombreux déploiements MySQL JSON tiennent la route.
  7. MVCC dans PostgreSQL signifie que les mises à jour créent de nouvelles versions de ligne ; les mises à jour volumineuses de JSON peuvent amplifier le bloat à moins que vacuum suive.
  8. Les formats de réplication comptent : le binlog row-based de MySQL et le décodage logique de PostgreSQL se comportent différemment sous des mises à jour JSON fréquentes et des lignes chaudes.

Sémantique JSON : ce que les moteurs stockent réellement

MySQL : JSON est un type, mais traitez-le comme un document sauf si vous projetez des champs

Le type JSON de MySQL n’est pas un « TEXT avec une étiquette ». Il est validé, stocké dans une représentation binaire, et manipulé avec des fonctions JSON.
C’est la bonne nouvelle. La nouvelle opérationnelle est que vous n’obtiendrez rarement des performances soutenues sauf si vous faites l’une des deux choses suivantes :
(1) garder le JSON majoritairement écrit une fois/lu par clé primaire, ou (2) extraire les chemins souvent interrogés dans des colonnes générées et les indexer.

MySQL vous laissera volontiers écrire une requête qui semble sélective mais n’est pas indexable. L’optimiseur fera ce qu’il peut, puis il balayera.
Vous pouvez parfois le sauver avec des index fonctionnels (selon la version) ou des colonnes générées, mais il faut être intentionnel.

PostgreSQL : JSONB pour interroger ; JSON (texte) pour préserver l’entrée exacte

PostgreSQL vous propose deux philosophies différentes :
json stocke le texte original (y compris espaces et ordre), et
jsonb stocke un format binaire décomposé optimisé pour les opérateurs et l’indexation.
Si vous voulez des performances, vous voulez presque toujours JSONB.

Les opérateurs de PostgreSQL sont expressifs : contenance (@>), existence (?, ?|, ?&),
extraction par chemin (->, ->>, #>, #>>), et requêtes JSON path.
Cette expressivité peut être un piège : on écrit des filtres astucieux qui semblent peu coûteux et finissent CPU-bound sur la décompression ou coincés sur un index qui ne correspond pas au prédicat.

Blague 1/2 : JSON, c’est comme un tiroir à bric‑à‑brac — tout rentre jusqu’au moment où il faut trouver les ciseaux.

Indexer le JSON : où la performance se gagne ou se perd

Indexation MySQL : les colonnes générées sont le geste adulte

Dans MySQL, l’indexation d’expressions JSON arbitraires s’est améliorée au fil du temps, mais le schéma opérationnel le plus fiable reste :
définir des colonnes générées pour les quelques chemins JSON que vous interrogez tout le temps, les caster en types scalaires stables, et les indexer.
Cela fait trois choses :

  • Donne à l’optimiseur un index B-tree normal qu’il comprend.
  • Évite des extractions JSON répétées à l’exécution.
  • Vous force à admettre quels champs font réellement partie du « vrai schéma ».

L’inconvénient : les changements de schéma deviennent plus lents et plus politiques, parce que le blob JSON a désormais des tentacules dans le DDL et les migrations.
Ce n’est pas un bug. C’est le prix à payer pour prétendre que des données semi-structurées ont une structure (parce qu’elles en ont une, une fois que vous en dépendez).

Quand l’indexation JSON MySQL échoue en pratique

  • Prédicats trop dynamiques (chemins JSON différents selon l’entrée utilisateur) vous poussent vers des scans.
  • Comparer des chaînes JSON à des nombres provoque des casts implicites et casse l’usage d’index.
  • Utiliser des fonctions dans WHERE sans expression indexable fait hausser les épaules de l’optimiseur et exécuter le travail lentement.

Indexation PostgreSQL : GIN est puissant, mais il faut choisir la classe d’opérateur

L’histoire d’indexation JSONB de PostgreSQL est plus solide, mais ce n’est pas magique. Les index GIN peuvent accélérer la contenance et les requêtes d’existence,
mais ils ont différentes classes d’opérateurs :

  • jsonb_ops : indexe plus de types d’opérations mais peut être plus volumineux.
  • jsonb_path_ops : plus compact et plus rapide pour la contenance, mais prend en charge moins d’opérateurs.

Si votre charge est « trouver des lignes où le JSON contient ces paires », jsonb_path_ops est souvent le bon choix.
Si vous avez besoin d’existence flexible et d’un support d’opérateurs plus large, jsonb_ops.
Choisissez mal, et vous aurez un index qui existe uniquement pour rendre VACUUM triste.

Indexes d’expression : le pont pratique entre JSON et relationnel

Si vous filtrez fréquemment sur un champ extrait (par exemple, payload->>'customer_id'), un index d’expression peut battre un GIN large
en taille et en prévisibilité. Il est aussi plus simple de raisonner sur la sélectivité.

Blague 2/2 : un index GIN, c’est comme la caféine — incroyable quand il est ciblé, regret quand on en abuse.

Schémas de requêtes qui séparent « ok » de « en feu »

Schéma 1 : « Récupérer par id et renvoyer le JSON » (plutôt sûr)

MySQL et PostgreSQL gèrent bien cela. Le coût dominant est l’I/O et la taille de ligne, pas les fonctions JSON.
Là où les équipes se blessent, c’est la lente progression : le JSON grossit, la taille des lignes augmente, l’efficacité du cache baisse, et soudain « lectures simples » deviennent des lectures disque.

Schéma 2 : « Filtrer par clés JSON à haute cardinalité » (indexez ou mourrez)

Si vous filtrez par user_id, tenant_id, order_id à l’intérieur du JSON, vous filtrez en fait par une clé relationnelle.
Ne faites pas semblant que c’est flexible. Promouvez-la : colonne générée + index dans MySQL, index d’expression dans Postgres, ou mieux encore faites-en une vraie colonne.
Ce n’est pas de l’idéologie. C’est pour éviter des scans complets et des plans de requête instables.

Schéma 3 : « Analytique ad hoc sur JSON » (méfiez‑vous de la lente progression)

JSON attire pour l’analytique parce qu’il est auto-descriptif. Dans des bases OLTP en production, c’est un piège.
L’analytique ad hoc tend à :

  • Utiliser des fonctions sur de nombreuses lignes, provoquant une consommation CPU.
  • Forcer des scans séquentiels parce que les prédicats ne correspondent pas aux index.
  • Sérialiser votre charge sur une grande table et un sous-système disque chaud.

Si le business veut de l’analytique, soit consacrez une réplique de reporting avec des garde‑fous plus stricts, soit streammez les événements ailleurs.
« Exécutez simplement sur prod » est une décision budgétaire déguisée en décision technique.

Schéma 4 : mises à jour partielles du JSON (lignes chaudes, journaux lourds)

Les deux bases peuvent mettre à jour des chemins à l’intérieur du JSON, mais les caractéristiques de performance diffèrent et l’impact opérationnel est similaire :
des mises à jour fréquentes sur de gros documents JSON signifient plus d’octets écrits, plus de churn d’index, plus de travail de réplication, et plus d’invalidation de cache.

La règle pratique : si un champ JSON est mis à jour fréquemment et lu fréquemment, il mérite une vraie colonne ou une table séparée.
JSON n’est pas un laissez‑passer gratuit à la normalisation ; c’est une facture différée.

Mises à jour, WAL/binlog et latence de réplication

MySQL : volume de binlog et réalités de la réplication basée sur les lignes

Dans MySQL, les grosses mises à jour JSON peuvent produire de grands événements binlog — surtout avec la réplication basée sur les lignes. Si vous mettez à jour de nombreuses lignes ou de gros
documents, vos réplicas en paieront le prix. La latence de réplication n’est rarement « un problème de réplica ». C’est un problème d’amplification d’écriture de l’application.

Surveillez aussi la taille des transactions et la fréquence des commits. Une charge qui met à jour le JSON par rafales peut créer des pics désagréables : pression fsync,
blocages de flush du binlog, et retard du thread SQL du réplica.

PostgreSQL : pression WAL + churn MVCC

PostgreSQL écrit du WAL pour les changements, et MVCC signifie que les mises à jour créent de nouvelles versions de lignes. Mettez à jour souvent un grand champ JSONB et vous obtiendrez :
plus de WAL, plus de tuples morts, plus de travail de vacuum, et potentiellement plus de bloat d’index.

La latence de réplication se manifeste par un backlog du WAL sender ou un délai de replay. L’important est de distinguer :
le réplica n’applique pas assez rapidement (CPU/I/O limités) vs
le primaire produit trop de WAL (amplification d’écriture).

Conseils opérationnels

  • Mesurez les octets WAL/binlog par seconde pendant les pics. C’est la chose la plus proche d’une « vérité » sur l’amplification d’écriture.
  • Partitionnez ou séparez les champs JSON chauds si les taux de mise à jour sont élevés.
  • Sur PostgreSQL, ajustez autovacuum pour les tables avec de nombreuses mises à jour JSON, sinon la dette de vacuum se traduira par une dette de latence.

Réalité du stockage et des E/S : bloat, usure de pages et comportement du cache

Taille de ligne et cache : votre taxe invisible

Les colonnes JSON rendent les lignes plus volumineuses. Des lignes plus grandes signifient moins de lignes par page. Moins de lignes par page signifie plus de lectures de pages pour le même nombre de lignes logiques.
Cela se manifeste par :

  • Une plus grande usure du buffer pool dans MySQL (InnoDB).
  • Plus d’usure des shared_buffers dans PostgreSQL.
  • Plus de pression sur le cache de pages du noyau.

La plupart des « régressions de performance mystérieuses » après l’ajout de JSON sont en fait « nous avons doublé la taille de ligne et personne n’a ajusté la mémoire ou les schémas d’accès. »

Bloat PostgreSQL : MVCC signifie que vous devez au collecteur vacuum

PostgreSQL n’écrase pas en place ; il crée de nouvelles versions de lignes. Si JSONB est volumineux et fréquemment mis à jour, les tuples morts s’accumulent et les index
churnent. Autovacuum peut gérer beaucoup de choses, mais il a besoin de seuils adaptés. Les réglages par défaut sont conçus pour être sûrs pour les débutants, pas optimaux pour votre désordre.

MySQL : index secondaires et pression undo/redo

InnoDB de MySQL a sa propre amplification d’écriture : redo logs, undo logs, doublewrite buffer, maintenance des index secondaires.
Les grosses mises à jour JSON augmentent les octets touchés et peuvent vous pousser vers des blocages de flush de logs. Vous le verrez comme des pics intermittents de latence,
des « commits soudainement lents », et des réplicas qui prennent du retard.

Tâches pratiques : 14 commandes que vous pouvez exécuter aujourd’hui

Ce sont le genre de commandes que j’exécute pendant un incident ou une revue de performance. Chaque tâche inclut :
la commande, ce que signifie la sortie, et la décision à prendre ensuite.
Les noms d’hôtes et chemins sont volontairement ennuyeux ; l’ennuyeux est reproductible.

Tâche 1 (MySQL) : confirmer l’utilisation de JSON et la pression de taille

cr0x@server:~$ mysql -e "SELECT table_schema, table_name, column_name, data_type FROM information_schema.columns WHERE data_type='json' ORDER BY table_schema, table_name;"
+--------------+------------+-------------+-----------+
| table_schema | table_name | column_name | data_type |
+--------------+------------+-------------+-----------+
| app          | events     | payload     | json      |
| app          | users      | attrs       | json      |
+--------------+------------+-------------+-----------+

Signification : vous savez maintenant quelles tables sont candidates à des problèmes liés au JSON.
Décision : retenez les 1–3 tables principales par nombre de lignes et taux de mise à jour. Ce sont celles où les choix d’indexation et de schéma comptent.

Tâche 2 (MySQL) : vérifier la taille des tables et l’empreinte des index

cr0x@server:~$ mysql -e "SELECT table_name, table_rows, ROUND(data_length/1024/1024,1) AS data_mb, ROUND(index_length/1024/1024,1) AS index_mb FROM information_schema.tables WHERE table_schema='app' ORDER BY data_length DESC LIMIT 10;"
+------------+------------+---------+----------+
| table_name | table_rows | data_mb | index_mb |
+------------+------------+---------+----------+
| events     |    4821031 |  8120.4 |   2104.7 |
| users      |     820114 |  1190.8 |    412.2 |
+------------+------------+---------+----------+

Signification : les tables lourdes en JSON ont tendance à gonfler data_mb.
Décision : si data_mb croît plus vite que la croissance commerciale, vous devez plafonner la taille des payloads, compresser en amont, ou normaliser les champs chauds.

Tâche 3 (MySQL) : identifier les prédicats JSON lents dans le slow log

cr0x@server:~$ sudo pt-query-digest /var/log/mysql/mysql-slow.log --limit 5
#  1.2s user time, 40ms system time, 27.31M rss, 190.55M vsz
# Query 1: 0.68 QPS, 0.31x concurrency, ID 0xA1B2C3D4 at byte 91234
# Time range: 2025-12-28T00:00:00 to 2025-12-28T01:00:00
# Attribute    pct   total     min     max     avg     95%  stddev  median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Exec time     62   180s    120ms     12s    540ms     3s   900ms   300ms
# Rows examine  90  1200M      10   2.5M   360k   1.1M   500k   200k
# Query: SELECT ... WHERE JSON_EXTRACT(payload,'$.customer.id') = ?

Signification : rows examined est votre « taxe de scan ». JSON_EXTRACT dans WHERE sans index est un coupable fréquent.
Décision : créez une colonne générée pour ce chemin (ou un index fonctionnel si approprié) et réécrivez la requête pour l’utiliser.

Tâche 4 (MySQL) : vérifier si une requête utilise un index

cr0x@server:~$ mysql -e "EXPLAIN SELECT id FROM app.events WHERE JSON_UNQUOTE(JSON_EXTRACT(payload,'$.customer.id'))='12345' LIMIT 10\G"
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: events
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 4821031
     filtered: 10.00
        Extra: Using where

Signification : type: ALL et pas de key signifie balayage complet de la table.
Décision : ne commencez pas par tuner les buffers. Corrigez le schéma/la requête : colonne générée + index, ou redesign.

Tâche 5 (MySQL) : ajouter une colonne générée pour un chemin JSON chaud

cr0x@server:~$ mysql -e "ALTER TABLE app.events ADD COLUMN customer_id VARCHAR(64) GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(payload,'$.customer.id'))) STORED, ADD INDEX idx_events_customer_id (customer_id);"
Query OK, 0 rows affected (2 min 41 sec)
Records: 0  Duplicates: 0  Warnings: 0

Signification : une colonne générée STORED matérialise la valeur, l’index devient utilisable.
Décision : réécrivez les requêtes applicatives pour filtrer par customer_id au lieu de JSON_EXTRACT dans WHERE. Puis re‑vérifiez EXPLAIN.

Tâche 6 (MySQL) : valider que l’optimiseur utilise maintenant le nouvel index

cr0x@server:~$ mysql -e "EXPLAIN SELECT id FROM app.events WHERE customer_id='12345' LIMIT 10\G"
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: events
         type: ref
possible_keys: idx_events_customer_id
          key: idx_events_customer_id
      key_len: 258
          ref: const
         rows: 120
        Extra: Using index

Signification : vous êtes passé de scanner des millions à toucher ~120 lignes.
Décision : déployez, puis surveillez la latence d’écriture : maintenir le nouvel index augmente le coût d’écriture.

Tâche 7 (MySQL) : vérifier la latence de réplication et la pression d’application

cr0x@server:~$ mysql -e "SHOW REPLICA STATUS\G" | egrep "Seconds_Behind_Source|Replica_SQL_Running|Replica_IO_Running|Last_SQL_Error"
Replica_IO_Running: Yes
Replica_SQL_Running: Yes
Seconds_Behind_Source: 87
Last_SQL_Error:

Signification : un retard existe même si les threads tournent. En général, l’application ne peut pas appliquer aussi vite que les écritures arrivent.
Décision : mesurez le débit binlog et la taille des transactions ; réduisez le volume de mises à jour JSON ou changez le batching avant de blâmer le réplica.

Tâche 8 (PostgreSQL) : lister les colonnes JSON/JSONB et leurs tables

cr0x@server:~$ psql -d appdb -c "SELECT table_schema, table_name, column_name, data_type FROM information_schema.columns WHERE data_type IN ('json','jsonb') ORDER BY 1,2,3;"
 table_schema | table_name | column_name | data_type
--------------+------------+-------------+-----------
 public       | events     | payload     | jsonb
 public       | users      | attrs       | jsonb
(2 rows)

Signification : périmètre. Comme MySQL : identifiez les quelques tables qui comptent le plus.
Décision : concentrez-vous d’abord sur les tables à taux de mise à jour élevé et requêtes orientées client.

Tâche 9 (PostgreSQL) : trouver les pires requêtes JSON par temps total

cr0x@server:~$ psql -d appdb -c "SELECT calls, total_exec_time::bigint AS total_ms, mean_exec_time::numeric(10,2) AS mean_ms, rows, query FROM pg_stat_statements WHERE query ILIKE '%jsonb%' OR query ILIKE '%->%' OR query ILIKE '%@>%' ORDER BY total_exec_time DESC LIMIT 5;"
 calls | total_ms | mean_ms | rows |                   query
-------+----------+---------+------+-------------------------------------------
 18211 |   932144 |   51.20 |    0 | SELECT ... WHERE payload @> $1
  4102 |   512030 |  124.82 |    0 | SELECT ... WHERE (payload->>'customer')= $1
(2 rows)

Signification : vous avez des requêtes chaudes, pas des théories.
Décision : lancez EXPLAIN (ANALYZE, BUFFERS) sur les principaux coupables et construisez le bon index pour la forme du prédicat.

Tâche 10 (PostgreSQL) : inspecter un plan de requête JSONB avec buffers

cr0x@server:~$ psql -d appdb -c "EXPLAIN (ANALYZE, BUFFERS) SELECT id FROM events WHERE payload @> '{\"customer\":{\"id\":\"12345\"}}'::jsonb LIMIT 10;"
                                                          QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.42..8.44 rows=10 width=8) (actual time=0.088..0.146 rows=10 loops=1)
   Buffers: shared hit=42
   ->  Index Scan using idx_events_payload_gin on events  (cost=0.42..22134.77 rows=26235 width=8) (actual time=0.086..0.141 rows=10 loops=1)
         Index Cond: (payload @> '{"customer": {"id": "12345"}}'::jsonb)
         Buffers: shared hit=42
 Planning Time: 0.412 ms
 Execution Time: 0.182 ms
(7 rows)

Signification : index scan + majoritairement des hits en buffer = sain.
Décision : conservez cet index s’il supporte des chemins produits essentiels. S’il ne sert qu’à des requêtes ad hoc, ne payez pas le coût en écriture.

Tâche 11 (PostgreSQL) : créer un GIN ciblé (choisir la classe d’opérateur)

cr0x@server:~$ psql -d appdb -c "CREATE INDEX CONCURRENTLY idx_events_payload_pathops ON events USING gin (payload jsonb_path_ops);"
CREATE INDEX

Signification : la création concurrente évite de bloquer les écritures (au prix du temps et d’un travail supplémentaire).
Décision : utilisez jsonb_path_ops lorsque la contenance domine ; sinon considérez jsonb_ops ou des index d’expression.

Tâche 12 (PostgreSQL) : construire un index d’expression pour un seul chemin chaud

cr0x@server:~$ psql -d appdb -c "CREATE INDEX CONCURRENTLY idx_events_customer_id_expr ON events ((payload->'customer'->>'id'));"
CREATE INDEX

Signification : cela rend les prédicats d’égalité sur cette valeur extraite prévisibles et peu coûteux.
Décision : si ce chemin est stable et largement utilisé, envisagez de le promouvoir en vraie colonne pour réduire le traitement JSON.

Tâche 13 (PostgreSQL) : vérifier les signaux de bloat et l’efficacité d’autovacuum

cr0x@server:~$ psql -d appdb -c "SELECT relname, n_live_tup, n_dead_tup, last_autovacuum, last_vacuum FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 5;"
 relname | n_live_tup | n_dead_tup |    last_autovacuum     |     last_vacuum
---------+------------+------------+------------------------+------------------------
 events  |    4809123 |     912044 | 2025-12-28 00:41:12+00 | 2025-12-22 03:11:02+00
 users   |     820104 |      12033 | 2025-12-28 00:38:01+00 | 2025-12-23 02:08:40+00
(2 rows)

Signification : les tuples morts sur events sont élevés ; autovacuum tourne, mais peut être sous-dimensionné pour ce churn de mises à jour.
Décision : ajustez les seuils autovacuum pour cette table, réduisez la fréquence des mises à jour sur de gros JSONB, ou séparez les champs mutables.

Tâche 14 (au niveau système) : identifier si vous êtes lié par I/O ou par CPU

cr0x@server:~$ iostat -x 1 5
Linux 6.1.0 (db01) 	12/29/2025 	_x86_64_	(16 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          22.11    0.00    6.34   18.90    0.00   52.65

Device            r/s     rkB/s   rrqm/s  %rrqm  r_await rareq-sz     w/s     wkB/s   w_await wareq-sz  aqu-sz  %util
nvme0n1         320.0  18240.0     0.0   0.00    4.20    57.00   410.0  24576.0    9.80    59.95   6.10   92.0

Signification : un %util élevé et un iowait significatif pointent vers une saturation du stockage. Les charges JSON gonflent souvent les E/S en raison de lignes plus grosses et du churn d’index.
Décision : corrigez d’abord les schémas de requête/index ; si la saturation persiste, augmentez les IOPS (disques plus performants) ou réduisez l’amplification d’écriture (changement de schéma/conception).

Playbook de diagnostic rapide

Quand les requêtes JSON deviennent lentes, les gens gaspillent des heures à discuter du « choix de la base » au lieu de trouver le véritable goulot d’étranglement.
Ce playbook est l’ordre dans lequel j’agirais lors d’un incident — parce qu’il converge rapidement.

Première étape : prouver s’il s’agit d’un scan, d’un miss d’index ou d’I/O brute

  • MySQL : exécutez EXPLAIN sur la requête lente. Si type: ALL, arrêtez et corrigez le prédicat/index.
  • PostgreSQL : exécutez EXPLAIN (ANALYZE, BUFFERS). Si vous voyez des scans séquentiels sur de grandes tables, il vous faut un index correspondant ou réécrire la requête.
  • Système : vérifiez iostat -x. Si le stockage est saturé, les scans et le bloat seront vos principaux suspects.

Deuxième étape : quantifier l’amplification d’écriture et la pression de réplication

  • MySQL : inspectez la latence de réplication et les patterns de croissance du binlog ; les grosses mises à jour JSON corrèlent souvent avec des pics de retard.
  • PostgreSQL : vérifiez la génération de WAL et les tuples morts ; des mises à jour JSON lourdes peuvent transformer vacuum en crise de fond permanente.

Troisième étape : vérifier l’efficacité du cache et la dérive de taille des lignes

  • Votre working set chaud est‑il toujours en mémoire, ou la croissance du JSON l’a‑t‑elle évincé ?
  • Avez‑vous ajouté un large index GIN qui a doublé le coût des écritures ?
  • Quelqu’un a‑t‑il commencé à faire des filtres ad hoc sur des clés JSON non indexées ?

Quatrième étape : corriger la plus petite chose qui change la courbe

  • Promouvez les clés chaudes en vraies colonnes (meilleur) ou en colonnes générées/index d’expression (second meilleur).
  • Ajoutez le bon index pour la forme du prédicat, puis validez avec EXPLAIN.
  • Si les mises à jour posent problème, séparez les champs mutables du blob JSON.

Erreurs courantes : symptômes → cause racine → correction

Erreur 1 : « La requête semble sélective mais est lente »

Symptômes : la latence croit avec la taille de la table ; EXPLAIN montre un scan complet ; CPU en pic pendant les heures de pointe.

Cause racine : extraction JSON dans WHERE sans expression indexable (MySQL), ou inadéquation entre opérateur et index (PostgreSQL).

Correction : MySQL : colonne générée STORED + index B-tree ; PostgreSQL : index d’expression ou bonne classe d’opérateur GIN ; réécrire le prédicat pour correspondre à l’index.

Erreur 2 : « Nous avons ajouté un index GIN et les écritures sont devenues plus lentes »

Symptômes : latence insert/update augmente ; taux WAL/binlog en pic ; latence de réplication après création de l’index.

Cause racine : index GIN large sur un JSONB volumineux avec mises à jour fréquentes ; coût élevé de maintenance d’index.

Correction : remplacez par des index d’expression plus étroits ; utilisez jsonb_path_ops si seule la contenance importe ; séparez les champs mutables ; repensez si la requête doit être sur l’OLTP.

Erreur 3 : « Postgres ralentit dans le temps ; vacuum n’arrive pas à suivre »

Symptômes : la taille des tables et index augmente ; les requêtes ralentissent ; autovacuum tourne constamment ; tuples morts élevés.

Cause racine : mises à jour fréquentes de gros JSONB créant de nombreux tuples morts ; seuils autovacuum non adaptés au churn de la table.

Correction : ajustez autovacuum par table ; réduisez la fréquence/taille des mises à jour ; déplacez les données mutables dans une table séparée ; envisagez le partitionnement pour les tables d’événements.

Erreur 4 : « Latence de réplication MySQL après ajout de fonctionnalités JSON »

Symptômes : Seconds_Behind_Source augmente pendant les rafales ; les réplicas récupèrent lentement ; les commits sont en pics.

Cause racine : grands événements binlog basés sur les lignes causés par des mises à jour JSON ; transactions surdimensionnées ; trop d’index secondaires sur des projections JSON.

Correction : réduisez le volume de mises à jour JSON ; changez le batching ; limitez les projections indexées aux vraies chemins chauds ; vérifiez les réglages binlog/redo et les patterns de commit.

Erreur 5 : « Nous avons tout stocké en JSON et maintenant nous avons besoin de contraintes »

Symptômes : valeurs incohérentes dans le JSON ; validations côté appli qui divergent ; les requêtes doivent gérer des clés manquantes et des types erronés.

Cause racine : schéma externalisé dans le code applicatif ; pas de contraintes appliquées ; migrations évitées jusqu’à trop tard.

Correction : promouvez les clés critiques en colonnes ; ajoutez des CHECK (Postgres) ou imposez via des colonnes générées + NOT NULL (MySQL) ; introduisez des payloads versionnés.

Trois mini-histoires d’entreprise depuis les tranchées JSON

1) Incident causé par une mauvaise hypothèse : « JSON est pratiquement gratuit à requêter »

Une entreprise SaaS de taille moyenne a déployé un « flux d’activité » soutenu par une table d’événements. Chaque événement avait un payload JSON.
L’équipe produit voulait filtrer : « montrer seulement les événements où payload.actor.role = ‘admin’. » Facile, pensaient‑ils.
Le backend utilisait MySQL, et la première implémentation utilisait JSON_EXTRACT dans la clause WHERE.

En staging c’était correct. En production, ce fut un désastre en accéléré : la table events était grosse, et le filtre était populaire.
La requête paraissait sélective, mais elle faisait un scan complet, touchant des millions de lignes par requête pendant les pics.
CPU saturé, I/O saturé, et tout le cluster a développé le symptôme « tout est lent » qui fait que les exécutifs rejoignent le canal d’incident.

L’hypothèse erronée n’était pas « MySQL ne peut pas faire du JSON. » C’était : « si le prédicat est étroit, la base l’optimisera. »
Les bases optimisent ce que vous indexez, pas ce que vous espérez. L’extraction JSON sans expression indexable n’est pas étroite ; c’est un calcul coûteux répété sur de nombreuses lignes.

La correction fut douloureusement simple : ajouter une colonne générée STORED pour actor_role, l’indexer, et changer la requête.
Le postmortem a ajouté une règle : toute clé JSON utilisée dans un WHERE chaud doit être projetée et indexée, ou déplacée en vraie colonne.
Le schéma flexible est resté, mais seulement là où il n’était pas sur le chemin critique.

2) Optimisation qui a échoué : « Ajoutons juste un gros index GIN »

Une autre entreprise utilisait PostgreSQL et avait une unique énorme table events avec des payloads JSONB.
Ils voulaient accélérer des recherches ad hoc pour le support client, alors quelqu’un a ajouté un large index GIN sur l’ensemble du payload en utilisant la classe d’opérateur par défaut.
La vitesse des requêtes s’est améliorée immédiatement. Tout le monde s’est félicité et est passé à autre chose.

Deux semaines plus tard, la latence d’écriture a commencé à augmenter. L’activité d’autovacuum est devenue constante. Un délai de réplication est apparu pendant les pics.
L’index GIN était coûteux à maintenir parce que les payloads étaient volumineux et mis à jour fréquemment avec des champs d’enrichissement.
L’index a aussi rapidement grossi, augmentant la pression sur checkpoints et les E/S. Le gain pour « recherche support » est devenu un problème « chaque endpoint API est plus lent ».

Le retour de bâton n’était pas que GIN soit mauvais. C’était qu’ils avaient indexé tout, pour une charge de requêtes qui n’était pas réellement centrale.
L’index a transformé la base en moteur de recherche. PostgreSQL peut le faire, mais vous payez en amplification d’écriture et bloat.

La correction finale : supprimer l’index large, ajouter deux index d’expression pour la poignée de clés utilisées dans les filtres du support,
et déplacer la recherche de type full-text hors du chemin OLTP. Le support a toujours sa capacité de recherche, mais la production n’a plus à payer la taxe sur chaque écriture.

3) Pratique ennuyeuse mais correcte qui a sauvé la mise : « Faire du JSON un contrat, le versionner et le tester »

Une équipe fintech stockait des métadonnées de vérification client en JSONB dans PostgreSQL. Cela incluait des champs imbriqués, des clés optionnelles et des blocs spécifiques à des fournisseurs.
Ils savaient que ces données allaient évoluer, et savaient aussi qu’ils auraient besoin d’interroger quelques champs de façon fiable pour des rapports de conformité.
Ils ont donc fait quelque chose d’ennuyeux : ils ont ajouté une colonne entière schema_version et écrit des migrations explicites pour les changements de forme du payload.

Ils ont aussi promu quelques champs critiques en vraies colonnes : customer_id, verification_status, et vendor_name.
Tout le reste vivait en JSONB. En plus, ils ont appliqué des CHECK garantissant que la colonne status correspondait à un ensemble connu,
et des tests applicatifs validant la compatibilité du schéma JSON par version.

Des mois plus tard, un fournisseur a changé subtilement son format de payload (un champ est devenu plus profond).
Les équipes qui conservent du JSON brut sans contrat découvrent généralement cela quand les rapports cassent à 2h du matin.
Cette équipe l’a découvert en CI, parce qu’un test de validation de schéma a échoué et que l’outil de migration a forcé une transformation explicite.

La pratique « ennuyeuse mais correcte » n’était pas un index sophistiqué. C’était traiter JSON comme un contrat versionné, pas comme un tiroir à bric‑à‑brac sans limite.
La production en a bénéficié : la performance des requêtes est restée stable, et la fréquence des incidents est restée faible — le type de victoire qui n’obtient jamais d’email de célébration.

Listes de contrôle / plan étape par étape

Si vous lancez une nouvelle fonctionnalité lourde en JSON

  1. Notez les 5 principaux schémas de requêtes que vous attendez dans les six prochains mois (pas seulement la semaine de lancement).
  2. Classifiez les champs : immuables vs mutables ; fréquemment filtrés vs rarement filtrés ; haute cardinalité vs faible cardinalité.
  3. Promouvez les champs « fréquemment filtrés, haute cardinalité » en vraies colonnes (préféré) ou en colonnes générées/index d’expression.
  4. Choisissez une stratégie d’indexation spécifique à la base :
    • MySQL : colonnes générées STORED + index B-tree ; évitez JSON_EXTRACT dans les WHERE chauds.
    • PostgreSQL : index d’expression pour les chemins chauds ; GIN pour contenance/existence ; sélectionnez la classe d’opérateur intentionnellement.
  5. Définissez des budgets de taille de payload (limites souples et strictes). La croissance du JSON est silencieuse jusqu’à ce qu’elle ne le soit plus.
  6. Planifiez l’évolution : ajoutez schema_version, documentez les transformations, et rendez les migrations routinières.

Si vous avez déjà déployé et que c’est lent

  1. Trouvez les 3 principales requêtes par temps total (slow log / pg_stat_statements).
  2. Exécutez EXPLAIN avec la réalité (MySQL EXPLAIN, Postgres EXPLAIN ANALYZE BUFFERS). Ne supposez pas.
  3. Ajoutez le plus petit index qui correspond au prédicat (index de colonne générée ou index d’expression) et vérifiez les changements de plan.
  4. Mesurez le coût côté écriture après indexation (latence de commit, taux WAL/binlog, latence de réplication).
  5. Si les mises à jour sont lourdes, séparez les champs mutables du JSON dans une table distincte avec une clé appropriée.
  6. Mettez des garde‑fous sur les requêtes ad hoc (timeouts, réplicas de lecture, ou un chemin de reporting dédié).

Si vous décidez entre MySQL et PostgreSQL pour JSON aujourd’hui

  • Choisissez PostgreSQL si l’interrogation JSON est une fonctionnalité produit, pas un détail d’implémentation.
  • Choisissez MySQL si JSON est surtout du stockage et que vous êtes prêt à projeter les clés chaudes dans des colonnes générées indexées.
  • Choisissez la base que votre équipe peut exploiter en conditions d’incident. Une fonctionnalité théoriquement supérieure ne réveillera pas votre personne d’astreinte à 3h du matin.

FAQ

1) PostgreSQL est-il toujours meilleur pour JSON que MySQL ?

Non. PostgreSQL est généralement meilleur pour les requêtes complexes et l’indexation flexible. MySQL peut être excellent si vous gardez l’usage JSON simple
ou si vous projetez les chemins chauds en colonnes générées indexées. « Toujours » est la façon dont commencent les incidents.

2) Dois‑je stocker JSON en TEXT/VARCHAR à la place ?

Généralement non. Vous perdez la validation et de nombreux opérateurs JSON. Si vous ne requêtez vraiment jamais à l’intérieur du JSON et que vous ne faites que stocker/retirer,
TEXT peut fonctionner — mais vous prenez un risque sur l’hygiène des données. Les types JSON natifs sont plus sûrs pour la correction.

3) Quand une clé JSON doit‑elle devenir une vraie colonne ?

Si elle est utilisée dans des jointures, dans des WHERE chauds, pour le tri, ou nécessaire pour des contraintes, c’est une colonne. Si elle est mise à jour fréquemment,
c’est probablement une colonne ou une table séparée. JSON sert à la variabilité, pas à l’identité centrale.

4) Les index GIN résolvent‑ils les performances JSONB dans PostgreSQL ?

Ils résolvent certains problèmes. Ils peuvent aussi en créer d’autres (coût en écriture, bloat, maintenance).
Utilisez GIN lorsque vos prédicats s’alignent sur la contenance/l’existence et que les données indexées sont suffisamment stables pour justifier le coût en écriture.

5) Quelle est l’équivalence MySQL d’un index GIN Postgres sur JSONB ?

Il n’y a pas d’équivalent direct. Dans MySQL, vous créez typiquement des colonnes générées qui extraient des valeurs scalaires et les indexent.
C’est une philosophie différente : vous décidez ce qui compte à l’avance.

6) Comment éviter « des clés aléatoires partout » dans le JSON ?

Traitez le JSON comme un contrat : versionnez‑le, validez‑le, et documentez les formes autorisées.
Faites respecter les invariants critiques avec des contraintes en base (Postgres) ou des colonnes générées + NOT NULL/casts (MySQL).

7) Pourquoi les mises à jour partielles de JSON restent-elles coûteuses ?

Parce qu’une « mise à jour partielle » au niveau SQL peut toujours signifier une réécriture substantielle et du churn d’index au niveau stockage,
plus du volume WAL/binlog. Les gros documents mis à jour fréquemment sont coûteux, quelle que soit la beauté du SQL.

8) Puis‑je utiliser JSON pour des données multi‑locataires et juste filtrer sur tenant_id dans le JSON ?

Vous pouvez, mais vous ne devriez pas. L’isolation des tenants appartient à une vraie colonne indexée.
Le mettre dans JSON facilite les scans accidentels entre tenants et rend plus difficile l’application des contraintes et des limites de performance.

9) Quel est le modèle hybride le plus sûr ?

Stockez les champs centraux en colonnes (ids, status, timestamps, clés étrangères), stockez les champs optionnels/spécifiques fournisseur en JSON/JSONB,
et indexez seulement le petit sous‑ensemble de chemins JSON que vous interrogez réellement. Le reste reste flexible sans entraîner le coût des requêtes centrales.

Conclusion : prochaines étapes qui ne vous feront pas honte plus tard

Le JSON dans MySQL et PostgreSQL n’est plus une nouveauté. C’est un outil de production — et comme tous les outils de production, il récompense la discipline.
MySQL tend à vouloir que vous projetiez la structure depuis le JSON et l’indexiez explicitement. PostgreSQL vous offre plus d’expressivité pour les requêtes et l’indexation,
mais il vous facturera en WAL, bloat et maintenance si vous indexez trop largement ou mettez à jour de gros JSONB trop souvent.

Prochaines étapes pratiques :

  1. Identifiez les 3 principales requêtes JSON par temps total et exécutez EXPLAIN avec des stats réelles.
  2. Promouvez les 3 principales clés JSON utilisées pour filtrer/jointer en colonnes ou en colonnes générées/index d’expression et indexez‑les.
  3. Mesurez l’amplification d’écriture (taux WAL/binlog) avant et après indexation ; surveillez la latence de réplication.
  4. Mettez en place un budget de taille de payload et faites‑le respecter à l’ingestion.
  5. Versionnez vos payloads JSON. Votre futur vous remerciera, sinon vous passerez un week‑end à décoder « pourquoi cette clé existe parfois. »

Choisissez la base qui correspond aux forces opérationnelles de votre équipe, puis concevez l’usage de JSON comme si vous vous attendiez à ce qu’il devienne permanent — parce que c’est souvent le cas.

ZFS SMB : corriger définitivement « Copie Windows lente »

Explorateur Windows indique « Copie… 112 MB/s » pendant trois secondes, puis passe à 0 B/s et reste figé comme s’il réfléchissait à sa vie. Les utilisateurs accusent « le réseau ». Le réseau renvoie la faute au « stockage ». Le stockage blâme « Windows ». Chacun a tort, mais de façons différentes.

Si vous exécutez SMB basé sur ZFS (généralement Samba sur Linux, parfois sur une appliance de stockage), vous pouvez rendre les copies Windows constamment rapides. Mais on n’y arrive pas en tournant des boutons au hasard. Il faut prouver d’où vient la latence, puis corriger la partie précise qui ment.

Ce que « copie lente » signifie réellement (et ce que ce n’est pas)

« Explorateur Windows lent » n’est pas un problème unique. C’est un symptôme visible par l’utilisateur d’un pipeline qui inclut : le comportement du client Windows, les sémantiques du protocole SMB, les détails d’implémentation de Samba, les groupes de transactions et chemins d’écriture de ZFS, et la latence du média physique. Votre tâche est de trouver l’étape qui transforme le débit en attente.

Les trois schémas de copie à dissocier

  • Copies séquentielles volumineuses (par ex., ISO, VHDX) : devraient se rapprocher du débit du lien jusqu’à ce que le serveur ne puisse plus engager les écritures.
  • Beaucoup de petits fichiers (par ex., arbres de source) : dominés par les métadonnées (create, setattr, close, rename), pas par le débit brut.
  • Workloads mixtes (partages personnels + VM + scanners) : « lent » est souvent un blocage tête-de-file : un mauvais motif ruine la file d’attente pour tout le monde.

Ce que ce n’est généralement pas

Ce n’est rarement « SMB est lent ». SMB3 peut être très rapide. Ce n’est rarement « ZFS est lent ». ZFS peut saturer des réseaux sérieux. C’est généralement des pics de latence causés par des écritures sync, des I/O aléatoires petits, une amplification des métadonnées, ou un mauvais alignement du cache, rendus visibles par un client qui rapporte des vitesses en rafales optimistes.

Un autre changement de perspective : Explorateur Windows n’est pas un outil de benchmark ; c’est un visualiseur d’anxiété. Ce graphique est plus un indicateur d’humeur qu’un oscilloscope.

Faits intéressants et contexte historique (pour comprendre le comportement)

  1. SMB1 vs SMB2/3 a tout changé. SMB2 (ère Vista/2008) a réduit la verbosité, permis des lectures/écritures plus grandes et le pipelining. Beaucoup d’histoires « SMB lent » sont en réalité « vous êtes coincé sur SMB1 ».
  2. Samba a commencé comme un projet d’ingénierie inverse. Il est passé de « faire communiquer UNIX et Windows » à un serveur SMB de niveau entreprise. Certains paramètres par défaut sont conservateurs parce que Samba doit survivre à des clients bizarres.
  3. Les écritures ZFS sont regroupées. ZFS engage les données par groupes de transactions (TXG). Cela rend le débit excellent, mais crée aussi un comportement en « pulsation » visible si la phase de commit cale.
  4. Les écritures sync sont une promesse, pas une sensation. Quand un client SMB demande de la durabilité, ZFS doit engager de façon sûre. Si votre pool ne peut pas faire des fsync à faible latence, vous obtenez le fameux graphe « rapide puis zéro ».
  5. Les durable handles et leases SMB ont modifié le comportement close/open. Les versions modernes de Windows mettent en cache agressivement. C’est bien, jusqu’à ce qu’une application impose des sémantiques de durabilité et transforme le caching en douleur synchrone.
  6. Le recordsize compte plus pour les partages que la plupart l’admettent. recordsize ZFS façonne l’amplification I/O. Un mauvais recordsize n’économise pas seulement l’espace — il force des IOPS supplémentaires sous accès aléatoire petit.
  7. La compression aide souvent SMB, même sur CPU rapide. Beaucoup de fichiers bureautiques se compressent bien, réduisant le coût disque et réseau. Le bénéfice est souvent sur la latence, pas le seul débit.
  8. Le signing SMB est devenu plus courant pour la sécurité. Activer le signing peut coûter du CPU. Le réglage « sécurisé » devient parfois « sécurisé mais lent » quand le serveur a un CPU faible ou un seul cœur saturé.

Feuille de route pour un diagnostic rapide

Ceci est l’ordre qui trouve rapidement le goulot d’étranglement, sans tomber dans le piège « on règle tout ».

Premier : classer le workload

  • Un gros fichier ? Beaucoup de petits fichiers ? Applications qui exigent la durabilité ?
  • La vitesse chute-t-elle à intervalles réguliers (toutes les quelques secondes) ? Ça sent les commits TXG.
  • Est-ce que ça n’arrive que sur certains partages ? Ça sent les propriétés du dataset ou des options de partage SMB.

Deuxième : décider si c’est réseau, CPU ou latence stockage

  • Réseau : erreurs d’interface, retransmissions, MTU incorrecte, hashing LACP défaillant, clients Wi‑Fi se faisant passer pour des serveurs.
  • CPU : un cœur saturé dans smbd, surcharge liée au signing/chiffrement, interruptions, saturation softirq.
  • Latence stockage : await élevé sur les vdevs, chemin sync ZFS bloqué, SLOG absent/lent, pool quasi plein ou fragmenté.

Troisième : valider le comportement sync (c’est souvent le coupable)

  • Vérifiez la propriété dataset sync et les paramètres Samba qui forcent le sync (par ex. strict sync).
  • Mesurez la latence fsync depuis l’hôte SMB lui‑même, pas depuis votre portable.
  • Si vous avez besoin de sémantiques sync, assurez‑vous d’avoir un SLOG approprié (protégé contre la perte d’alimentation) ou acceptez les limites de performance de vos vdevs principaux.

Quatrième : isoler le cas « beaucoup de petits fichiers »

  • Les métadonnées sont le workload. Vérifiez atime, le comportement des xattr et les performances en petits blocs.
  • Vérifiez que le layout du pool correspond aux attentes IOPS pour les métadonnées (trade‑off mirrors vs RAIDZ).

Cinquième : ne tuner que ce que les mesures impliquent

Si vous ne pouvez pas montrer un avant/après sur la latence I/O, l’utilisation CPU ou les retransmissions, vous ne faites pas du tuning — vous décorez.

Arrêtez de deviner : mesurer où passe le temps

Les copies SMB sont une négociation entre un client qui met en tampon et un serveur qui engage. Explorateur rapporte la « vitesse » basée sur la rapidité d’acceptation dans les tampons, pas sur la vitesse d’écriture durable. Pendant ce temps, ZFS peut accepter les données rapidement dans l’ARC et les tampons sales, puis faire une pause pendant le commit des TXG. Cette pause est là où le graphe tombe à zéro.

Votre plan de mesure doit répondre à trois questions :

  1. Le client attend‑il le serveur (latence), ou n’envoie‑t‑il pas (throttling côté client) ?
  2. Le serveur attend‑il des flush disque (chemin sync), ou du CPU (signing/chiffrement), ou le réseau ?
  3. ZFS amplifie‑t‑il le workload (mismatch recordsize, fragmentation, pression sur les métadonnées) ?

L’ingénierie de la fiabilité a une règle simple applicable ici : mesurez le système que vous avez, pas celui que vous voudriez avoir.

Idée paraphrasée (Gene Kim) : « Améliorer le flux signifie trouver et supprimer la contrainte. » C’est tout le jeu.

Réelités ZFS qui gênent SMB

TXG et le motif « rapide puis zéro »

ZFS accumule des données sales en mémoire et les commit périodiquement sur disque comme un groupe de transactions. Si la phase de commit prend trop de temps, le système bride les écrivains. Du point de vue du client : rafale rapide, puis blocage. Répéter. Ce n’est pas « jitter réseau ». C’est la durabilité stockage qui rattrape son retard.

Écritures sync : la taxe de durabilité

Quand le workload émet des écritures synchrones (ou que le serveur les traite ainsi), ZFS doit garantir que les données sont sur un stockage stable avant d’acquitter. Sur des pools sans device intent log rapide, les écritures sync frappent vos vdevs principaux. Si ceux‑ci sont en RAIDZ sur HDD, vous pouvez prédire le résultat : douleur avec horodatage.

Recordsize, ashift et amplification I/O

Le recordsize ZFS contrôle la taille maximale de bloc pour les données de fichier. Les partages SMB stockent souvent des tailles de fichiers mixtes ; un recordsize trop grand n’altère pas toujours les lectures séquentielles, mais peut nuire aux écritures aléatoires et aux réécritures partielles. Trop petit peut augmenter la surcharge de métadonnées et réduire l’efficacité de la compression.

Les métadonnées ne sont pas « gratuites »

Les copies de petits fichiers stressent les métadonnées : entrées de répertoire, ACL, xattrs, horodatages. ZFS peut gérer cela correctement, mais seulement si le layout du pool et le caching sont sensés. Si vous avez construit un large RAIDZ pour la capacité puis l’avez converti en partage SMB chargé en métadonnées, vous avez essentiellement acheté un bus et l’avez inscrit à une course de motos.

Remplissage du pool et fragmentation

À mesure que les pools se remplissent, l’allocation devient plus difficile, la fragmentation augmente et la latence grimpe. Les utilisateurs SMB vivent cela comme « c’était bien le mois dernier ». ZFS n’oublie pas soudainement comment écrire ; il manque des endroits faciles pour placer les blocs.

Réelités SMB qui gênent ZFS

Les sémantiques de copie Windows : buffering, close et durabilité

Windows peut mettre en tampon les écritures et n’imposer la durabilité qu’à la fermeture du fichier, selon les flags de l’application et la configuration du serveur. Certaines applis (et certains outils de sécurité) demandent des écritures write‑through. Cela transforme instantanément votre workload de « principalement async » en « lourdement sync ».

Signing et chiffrement : la sécurité a un coût CPU

Le signing SMB est souvent imposé par une politique. Le chiffrement peut être activé pour certains partages. Les deux consomment du CPU. Si votre serveur SMB a un CPU modeste avec une NIC rapide, vous pouvez atteindre un plafond où le réseau est inactif et un cœur sue en crypto.

SMB3 Multichannel : excellent quand ça marche, inutile sinon

Multichannel peut utiliser plusieurs NIC et files RSS. Mal configuré, vous obtenez exactement un flux TCP bloqué sur une file. Alors quelqu’un dit « mais on a du dual 10GbE » comme si le serveur était obligé de s’en préoccuper.

Oplocks opportunistes, leases et antivirus

Le caching client (oplocks/leases) réduit la verbosité. Mais les scanners de sécurité endpoint adorent ouvrir des fichiers, forcer des mises à jour d’attributs et casser le comportement de cache. Cela peut transformer une copie « beaucoup de petits fichiers » en un festival d’appels système.

Blague #1 : le dépannage SMB ressemble à la politique de bureau — tout le monde insiste pour être le goulot d’étranglement, et d’une certaine façon ils ont tous raison.

Tâches pratiques : commandes, sorties, décisions

Ci‑dessous des tâches réelles à exécuter sur le serveur SMB/ZFS. Chacune inclut ce que la sortie signifie et la décision suivante à prendre. Ces conseils sont orientés Linux + Samba + OpenZFS, parce que c’est là que vivent la plupart des tickets « Explorateur Windows lent ».

Task 1: Confirm pool health (because performance is often a symptom of a dying disk)

cr0x@server:~$ sudo zpool status -v tank
  pool: tank
 state: ONLINE
  scan: scrub repaired 0B in 04:12:19 with 0 errors on Tue Dec 10 03:20:01 2025
config:

        NAME                        STATE     READ WRITE CKSUM
        tank                        ONLINE       0     0     0
          mirror-0                  ONLINE       0     0     0
            ata-SAMSUNG_SSD_1TB_A   ONLINE       0     0     0
            ata-SAMSUNG_SSD_1TB_B   ONLINE       0     0     0

errors: No known data errors

Signification : « ONLINE » et un scrub propre signifie que vous ne luttez pas contre des tentatives silencieuses ou une resilver en cours. Si vous voyez DEGRADED, resilvering ou checksum errors, arrêtez le tuning et corrigez d’abord le matériel.

Décision : Si un vdev montre des erreurs ou une resilver, planifiez une remédiation et retestez les performances après stabilisation.

Task 2: Check pool fullness (near-full pools get slow in boring, predictable ways)

cr0x@server:~$ zfs list -o name,used,avail,refer,mountpoint tank
NAME   USED  AVAIL  REFER  MOUNTPOINT
tank   38.2T 2.1T   96K    /tank

Signification : ~95% utilisé (38.2T used, 2.1T avail) est un territoire dangereux pour beaucoup de workloads. L’allocation devient contrainte ; la fragmentation augmente.

Décision : Si vous êtes au‑dessus de ~80–85% utilisé et que la performance compte, planifiez de récupérer de l’espace ou d’étendre. Aucun réglage Samba ne battra la physique.

Task 3: Identify which dataset backs the SMB share and dump its key properties

cr0x@server:~$ sudo zfs get -H -o property,value recordsize,compression,atime,sync,xattr,acltype,primarycache,logbias tank/shares/engineering
recordsize	1M
compression	lz4
atime	on
sync	standard
xattr	sa
acltype	posixacl
primarycache	all
logbias	latency

Signification : Vous avez recordsize=1M (bon pour gros fichiers séquentiels, risqué pour réécritures partielles), atime activé (écritures métadonnées supplémentaires), sync standard (sync honoré), xattr en SA (souvent bon), logbias latency (préférence SLOG si présent).

Décision : Si le partage est « beaucoup de petits fichiers », envisagez recordsize=128K et atime=off. Si ce sont des images VM, traitez différemment (et probablement pas via SMB).

Task 4: Measure pool I/O latency during a copy (the truth is in iostat)

cr0x@server:~$ sudo zpool iostat -v tank 1 5
                              capacity     operations     bandwidth
pool                        alloc   free   read  write   read  write
--------------------------  -----  -----  -----  -----  -----  -----
tank                        38.2T  2.1T     12   2400   3.1M   210M
  mirror-0                  38.2T  2.1T     12   2400   3.1M   210M
    ata-SAMSUNG_SSD_1TB_A      -      -      6   1250   1.6M   108M
    ata-SAMSUNG_SSD_1TB_B      -      -      6   1150   1.5M   102M
--------------------------  -----  -----  -----  -----  -----  -----

Signification : Un grand nombre d’opérations d’écriture (2400/s) avec une bande passante modérée suggère des écritures petites ou un comportement sync‑heavy. Si la bande passante est faible mais les ops élevées, vous êtes lié aux IOPS ou aux flush.

Décision : Si les écritures sont petites et fréquentes, investiguez les sémantiques sync, la charge sur les métadonnées et un mismatch recordsize. Si les ops sont faibles et la bande passante faible, suspectez le réseau ou un throttling SMB.

Task 5: Observe per-vdev latency with iostat (await is the smoke alarm)

cr0x@server:~$ sudo iostat -x 1 3
Linux 6.6.15 (server) 	12/25/2025 	_x86_64_	(16 CPU)

Device            r/s     w/s   rkB/s   wkB/s  avgrq-sz avgqu-sz   await  r_await  w_await  svctm  %util
nvme0n1           2.0   950.0    64.0 118000.0   248.0     3.20    3.4     1.2      3.4    0.6   58.0
nvme1n1           1.0   910.0    32.0 112000.0   246.0     3.05    3.3     1.1      3.3    0.6   55.0

Signification : ~3.3ms d’await en écriture est acceptable pour NVMe. Si vous voyez des dizaines/centaines de ms pendant les copies, le stockage bride votre débit.

Décision : await élevé + CPU bas + réseau propre = problème chemin stockage (écritures sync, pool plein, vdevs lents, ou problèmes SLOG).

Task 6: Check whether you even have a SLOG (and whether it’s doing anything)

cr0x@server:~$ sudo zpool status tank | sed -n '1,120p'
  pool: tank
 state: ONLINE
config:

        NAME                         STATE     READ WRITE CKSUM
        tank                         ONLINE       0     0     0
          mirror-0                   ONLINE       0     0     0
            ata-SAMSUNG_SSD_1TB_A    ONLINE       0     0     0
            ata-SAMSUNG_SSD_1TB_B    ONLINE       0     0     0
        logs
          nvme-SLOG_INTEL_OPTANE     ONLINE       0     0     0

Signification : Il y a un device log séparé. Bien. Mais l’existence n’est pas la performance ; il doit être rapide et protégé contre la perte d’alimentation.

Décision : Si vous avez des workloads sync‑heavy et pas de SLOG, décidez si vous avez besoin des sémantiques sync. Si oui, ajoutez un SLOG adéquat. Si non, n’essayez pas de le simuler avec sync=disabled à moins d’accepter la perte d’écritures reconnues en cas de coupure.

Task 7: Watch TXG throttling and dirty data behavior

cr0x@server:~$ sudo arcstat 1 3
    time  read  miss  miss%  dmis  dm%  pmis  pm%  mmis  mm%  arcsz     c  avail
12:10:01   320    12      4     1    0    11    3     0    0   84.2G  96.0G  21.4G
12:10:02   410    16      3     2    0    14    3     0    0   84.2G  96.0G  21.4G
12:10:03   390    10      2     1    0     9    2     0    0   84.2G  96.0G  21.4G

Signification : L’ARC ne manque pas trop ; le caching n’est pas votre contrainte actuelle. Si l’ARC est petit par rapport à la mémoire, ou si les misses grimpent, vous pourriez être limité par la mémoire ou mal configuré.

Décision : Si les misses ARC sont élevées pendant les lectures, ajustez la taille mémoire/ARC ou le working set (ou acceptez que le workload ne se cache pas bien).

Task 8: Confirm Samba version and whether SMB3 is negotiated

cr0x@server:~$ smbd -V
Version 4.19.5-Debian

cr0x@server:~$ sudo smbstatus -b | sed -n '1,60p'
Samba version 4.19.5-Debian
PID     Username     Group        Machine                                   Protocol Version  Encryption           Signing
-------------------------------------------------------------------------------------------------------------------------
23144   user1        domain users  10.10.20.55 (ipv4:10.10.20.55:53122)     SMB3_11           -                    partial

Signification : SMB3_11 est négocié, bien. Le signing est « partial » (dépend de la config et du client). Si vous voyez SMB1, vous avez des problèmes plus graves que le tuning.

Décision : Si SMB1 apparaît, désactivez‑le et corrigez la compatibilité client de façon intentionnelle. Ne conservez pas SMB1 « pour ce vieux scanner ». Remplacez le scanner ou isolez‑le.

Task 9: Check Samba share configuration for sync killers (strict sync, sync always)

cr0x@server:~$ sudo testparm -sv | sed -n '/^\[engineering\]/,/^\[/{p}'
[engineering]
	path = /tank/shares/engineering
	read only = No
	vfs objects = acl_xattr
	strict sync = Yes
	sync always = No

Signification : strict sync = Yes force Samba à flusher sur plus d’opérations. C’est un classique « on l’a activé pour la sécurité » qui peut anéantir le débit.

Décision : Si vous n’avez pas une exigence de conformité stricte, mettez strict sync = No et validez le comportement des applications. Si vous en avez besoin, investissez dans un SLOG et un stockage à faible latence.

Task 10: Check whether SMB signing/encryption is on and whether CPU is the limiter

cr0x@server:~$ sudo smbstatus -b | awk 'NR==1 || NR==2 || $0 ~ /SMB3/'
Samba version 4.19.5-Debian
PID     Username     Group        Machine                                   Protocol Version  Encryption           Signing
23144   user1        domain users  10.10.20.55 (ipv4:10.10.20.55:53122)     SMB3_11           AES-128-GCM          mandatory

cr0x@server:~$ top -b -n 1 | sed -n '1,20p'
top - 12:12:41 up 34 days,  3:01,  2 users,  load average: 9.12, 8.40, 7.95
Tasks: 291 total,   2 running, 289 sleeping,   0 stopped,   0 zombie
%Cpu(s): 12.1 us,  2.0 sy,  0.0 ni, 78.0 id,  0.0 wa,  0.0 hi,  7.9 si,  0.0 st
MiB Mem :  256000.0 total,  21000.0 free,  95000.0 used, 140000.0 buff/cache

Signification : Le chiffrement est activé. Le CPU est majoritairement inactif ici, donc le chiffrement n’est probablement pas le goulot à cet instant. Si vous voyez un cœur saturé et un softirq élevé, revoyez la question.

Décision : Si le chiffrement/signing est obligatoire et que le CPU chauffe, améliorez le CPU, utilisez des systèmes compatibles AES‑NI, assurez‑vous que RSS et multiqueue sont configurés, ou restreignez le chiffrement à certains partages sensibles.

Task 11: Verify NIC link, duplex, and error counters (cheap checks, expensive consequences)

cr0x@server:~$ ip -s link show dev bond0
2: bond0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9000 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 9c:dc:71:aa:bb:cc brd ff:ff:ff:ff:ff:ff
    RX:  bytes packets errors dropped  missed   mcast
    1223344556677 1023344556      0       0       0  120034
    TX:  bytes packets errors dropped carrier collsns
    1334455667788 1124455667      0       0       0       0

cr0x@server:~$ ethtool bond0 | sed -n '1,25p'
Settings for bond0:
	Supported ports: [ ]
	Supported link modes:   Not reported
	Speed: 20000Mb/s
	Duplex: Full
	Auto-negotiation: off

Signification : Pas d’erreurs, duplex complet, vitesse attendue. Si vous voyez des erreurs ou des paquets perdus, corrigez le réseau avant de toucher à ZFS.

Décision : Si des erreurs existent : vérifiez le câblage, les ports du switch, la cohérence MTU et les paramètres offload. Le tuning de performance sur une perte de paquets est du théâtre.

Task 12: Check TCP retransmits and socket pressure (SMB over a sick network is a lie)

cr0x@server:~$ ss -s
Total: 884
TCP:   211 (estab 104, closed 72, orphaned 0, timewait 72)

Transport Total     IP        IPv6
RAW	  0         0         0
UDP	  11        8         3
TCP	  139       113       26
INET	  150       121       29
FRAG	  0         0         0

cr0x@server:~$ netstat -s | sed -n '1,80p'
Tcp:
    154239 active connection openings
    149802 passive connection openings
    1124 failed connection attempts
    1821 connection resets received
    0 connections established
    224159 segments received
    231008 segments sent out
    214 segments retransmitted

Signification : Des retransmissions existent mais pas de façon délirante. Si les retransmissions montent pendant les copies, vous verrez des blocages indépendants du stockage. SMB est sensible aux pics de latence.

Décision : Retransmissions élevées : inspectez les buffers du switch, la mismatch MTU, le driver/firmware NIC, ou un chemin de firewall surchargé.

Task 13: Identify whether the workload is sync-heavy (server-side fsync test)

cr0x@server:~$ sync; sudo bash -c 'time dd if=/dev/zero of=/tank/shares/engineering/.fsync-test bs=1M count=256 conv=fdatasync status=none'
real	0m1.92s
user	0m0.00s
sys	0m0.28s

cr0x@server:~$ sudo rm -f /tank/shares/engineering/.fsync-test

Signification : Ceci mesure « écrire puis forcer la durabilité ». Si c’est lent (par ex., 10–60s), votre pool ne peut pas commit les écritures sync assez vite pour des workloads SMB qui les exigent.

Décision : fsync lent : ajoutez/validez un SLOG, réduisez les sync forcés dans Samba si acceptable, ou redessinez le stockage pour des écritures à faible latence.

Task 14: Confirm dataset is not accidentally forcing sync off (or on) where you didn’t intend

cr0x@server:~$ sudo zfs get -H -o name,property,value sync tank/shares/engineering tank/shares/finance
tank/shares/engineering	sync	standard
tank/shares/finance	sync	always

Signification : Le partage finance est forcé en sync=always. Cela peut être intentionnel (apps nécessitant la durabilité) ou une mauvaise configuration qui le rend lent.

Décision : Si sync=always existe, confirmez auprès des propriétaires d’application pourquoi. Si personne ne le justifie, revenez à standard et testez.

Task 15: Check ZFS compression and actual ratio (because “we enabled compression” is not the same as “it’s working”)

cr0x@server:~$ zfs get -H -o name,property,value compression,compressratio tank/shares/engineering
tank/shares/engineering	compression	lz4
tank/shares/engineering	compressratio	1.62x

Signification : 1.62x signifie que vous économisez I/O et espace. Si le ratio est ~1.00x, la compression n’aide pas beaucoup mais n’handicape généralement pas avec LZ4.

Décision : Gardez LZ4 presque toujours. Désactivez uniquement si vous avez mesuré une saturation CPU et des données presque incompressibles.

Task 16: Look for pathological fragmentation (especially if pool is old and near full)

cr0x@server:~$ sudo zdb -bbbs tank | sed -n '1,40p'
Block Size Histogram:
 512: 0
 1K : 0
 2K : 1048576
 4K : 2097152
 8K : 1048576
 16K: 524288
 32K: 262144
 64K: 131072
 128K: 65536
 256K: 32768
 512K: 16384
 1M : 8192

Signification : C’est une vue approximative ; dans la pratique vous corrélerez la fragmentation avec le comportement d’allocation et la latence. Une forte diversité de petits blocs sur un dataset prévu pour des écritures séquentielles larges peut être un indice.

Décision : Si la fragmentation et le remplissage sont élevés, planifiez une migration de données ou une expansion du pool. ZFS est excellent, mais il ne se défragmente pas par la pensée.

Trois micro-récits du monde de l’entreprise

Mini‑récit 1 : L’incident causé par une mauvaise hypothèse

Ils avaient un tout nouveau serveur de fichiers ZFS et deux uplinks 10GbE. Le déploiement semblait correct la première semaine, surtout parce que le test était « copier une ISO de 20GB une fois » et tout le monde est parti content.

Puis la clôture trimestrielle est arrivée. Finance a poussé des milliers de petits PDFs et feuilles de calcul dans un partage depuis une application Windows qui insistait sur le write‑through. Les utilisateurs ont signalé des copies « qui calaient toutes les quelques secondes ». L’équipe réseau n’a vu aucune saturation, donc elle s’est déclarée victorieuse et a accusé Windows. L’équipe stockage voyait beaucoup de RAM libre et a supposé que l’ARC lisserait tout. Ce ne fut pas le cas.

L’hypothèse erronée était subtile : « Si le pool peut faire 1GB/s en écriture séquentielle, il peut faire les copies de fichiers bureau. » Ce sont deux sports différents. Le débit séquentiel est un tour de victoire ; les métadonnées sync‑heavy sont le parcours d’obstacles.

Quand quelqu’un a lancé un simple test dd ... conv=fdatasync sur le dataset, c’est devenu évident. La latence de commit sync était le goulot. Le pool était en RAIDZ sur HDD. Parfait pour la capacité, terrible pour les écritures à faible latence.

La correction fut aussi subtile : ils n’ont pas désactivé le sync. Ils ont ajouté un SLOG approprié, protégé contre la perte d’alimentation, et retiré strict sync des partages qui n’en avaient pas besoin. Finance a conservé ses garanties ; engineering a retrouvé sa vitesse. Les tickets d’assistance se sont arrêtés — c’est la seule KPI qui compte quand on est d’astreinte.

Mini‑récit 2 : L’optimisation qui s’est retournée contre eux

Une autre entreprise avait des copies lentes de répertoires personnels. Quelqu’un a lu un fil de forum et a décidé que la solution était « plus grand recordsize = plus rapide ». Ils ont donc mis recordsize=1M sur tous les datasets SMB, y compris home dirs et arbres de projet partagés.

Les copies de gros fichiers se sont améliorées légèrement. Ensuite les plaintes sont devenues plus étranges : sauvegarder de petits documents semblait saccadé, l’accès à Outlook PST est devenu instable, et certaines applis « ne répondaient plus » lors des enregistrements. Le serveur SMB n’était pas tombé ; il faisait juste beaucoup d’opérations supplémentaires.

Pourquoi ? Les réécritures partielles sur de grands records peuvent créer une amplification d’écriture. Un petit changement dans un fichier peut déclencher un read‑modify‑write d’un gros bloc, surtout quand le workload est aléatoire et que l’appli fait beaucoup de petites mises à jour. ZFS est copy‑on‑write, donc il fait déjà un travail prudent ; ajouter de l’amplification, c’est comme lui demander de jongler sur un tapis roulant.

L’« optimisation » a aussi aggravé le churn des métadonnées parce que les profils utilisateurs génèrent un tas de petits fichiers et de mises à jour d’attributs. Un recordsize plus grand n’aidait pas du tout le chemin métadonnées. Il a juste rendu le chemin données moins amiable.

Le rollback fut discipliné : ils ont séparé les datasets selon le workload. Les home dirs sont passés à 128K recordsize, atime off. Les archives projets/media sont restées à 1M. Les performances se sont stabilisées. La leçon : le tuning n’est pas un buffet où on empile tout ce qui semble appétissant.

Mini‑récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise

Une équipe exploitant un cluster ZFS + Samba avait une habitude peu glamour : rapports de scrub hebdomadaires et snapshots de performance mensuels. Pas des tableaux de bord pour l’exécutif. Juste un fichier texte avec zpool status, zpool iostat sous charge, et les compteurs d’erreurs NIC de base.

Un mardi, les utilisateurs ont signalé que les copies étaient devenues « saccadées ». L’ingénieur d’astreinte n’a pas deviné. Il a extrait la baseline et l’a comparée aux chiffres actuels. Le grand changement : la latence d’écriture sur une jambe de mirror avait dérivé vers le haut, et des erreurs corrigeables apparaissaient — juste assez pour déclencher des retries, pas assez pour faire échouer le disque.

Parce qu’ils avaient des données de référence, ils n’ont pas passé une demi‑journée à se disputer sur des flags Samba. Ils ont remplacé le disque pendant une fenêtre de maintenance, resilveré, et les blocages de copie ont disparu.

Rien d’héroïque. Aucun réglage magique. Juste remarquer qu’une « régression de performance » est souvent un vieillissement matériel lent. Voilà à quoi ressemble la compétence en production.

Décisions de réglage qui déplacent vraiment l’aiguille

1) Décidez explicitement de votre position sur le sync (ne laissez pas cela vous arriver)

Les workloads SMB peuvent être sync‑heavy, surtout avec certaines applications et politiques. Vous avez trois choix :

  • Respecter le sync et en payer le prix : garder sync=standard, éviter les paramètres Samba qui forcent des flushs supplémentaires, et déployer un vrai SLOG si nécessaire.
  • Forcer sync toujours : sync=always pour des partages conformes. Attendez‑vous à un débit plus faible ; concevez le stockage en conséquence.
  • Désactiver le sync : sync=disabled est une décision métier qui accepte la perte d’écritures acquittées en cas de coupure. Cela peut être valide pour des partages temporaires, mais ne prétendez pas que c’est un « gain de performance gratuit ». C’est un contrat de durabilité différent.

2) Séparez les datasets par workload (un partage, un comportement)

Un seul dataset pour tout est la manière la plus rapide de s’assurer que rien ne va bien. Séparez :

  • Home directories (métadonnées lourdes, petits fichiers)
  • Arbres projets engineering (beaucoup de petits fichiers, lecture majoritaire)
  • Archives médias (gros séquentiels)
  • Zones de dépôt applicatives (peuvent nécessiter une durabilité stricte)

Puis définissez les propriétés par dataset : recordsize, atime, sync, compression, comportement ACL.

3) Choisissez un recordsize suffisamment adapté

  • Partages SMB généraux : commencez par recordsize=128K.
  • Archives de gros fichiers : envisagez recordsize=1M si la majorité des fichiers sont gros et séquentiels.
  • Bases de données/VM via SMB : évitez si possible ; si vous devez, utilisez des réglages spécialisés et testez rigoureusement. Le service de fichiers SMB et un datastore VM ne font pas bon ménage par hasard.

4) Désactivez atime pour les partages SMB (sauf raison réelle)

atime=on ajoute des écritures métadonnées sur les lectures. La plupart des organisations n’utilisent pas l’heure d’accès pour quelque chose d’utile, et Windows n’a certainement pas besoin que votre serveur ZFS écrive des métadonnées supplémentaires à chaque ouverture de fichier.

5) Gardez la compression LZ4 activée par défaut

LZ4 est un des rares « par défaut » que je défends en production. Il améliore souvent le débit effectif et réduit l’I/O. Ne vous compliquez pas la vie sauf si vous avez des preuves d’une saturation CPU.

6) Utilisez un vrai SLOG quand vous en avez besoin (et ne lésinez pas)

Un device SLOG n’est pas « n’importe quel SSD ». Il doit avoir une faible latence sous charge d’écriture sync et une protection contre la perte d’alimentation. Sinon vous avez construit un générateur de latence coûteux.

7) Samba : évitez « strict sync » sauf justification

strict sync peut détruire le débit pour des workloads qui génèrent beaucoup de points fsync (y compris certains comportements Windows autour de la fermeture de fichier). Si vous avez besoin de sémantiques strictes, rendez le stockage capable. Sinon, ne payez pas pour cela.

8) Signing/chiffrement SMB : ciblez-les

Les équipes sécurité aiment les politiques globales. Les systèmes de production aiment les budgets. Si signing/chiffrement doivent être obligatoires, assurez‑vous que l’hôte SMB a de la marge CPU et un matériel crypto moderne. Si seules certaines partages contiennent des données sensibles, ciblez la politique par partage ou par segment de trafic.

Blague #2 : Rien ne rend un serveur de fichiers plus rapide comme une réunion de politique qui se termine par « nous n’avons rien changé ».

Erreurs courantes : symptôme → cause racine → correctif

1) Symptom: Copy starts fast, then drops to 0 B/s repeatedly

Cause racine : stalls de commit TXG dus aux écritures sync ou à une latence de flush lente (pas de SLOG, vdevs lents, pool trop plein).

Correctif : Mesurez fsync (dd ... conv=fdatasync), vérifiez les paramètres sync Samba, ajoutez un SLOG approprié ou redessinez le pool pour la latence, récupérez de l’espace.

2) Symptom: Large files copy fine; many small files crawl

Cause racine : Workload lié aux métadonnées (ACL, xattrs, timestamps) plus limites en petits I/O aléatoires.

Correctif : atime=off, assurez‑vous de propriétés dataset appropriées, envisagez des mirrors pour les pools lourds en métadonnées, vérifiez que les modules VFS Samba n’ajoutent pas de surcharge, et acceptez qu’il s’agisse d’IOPS et non de bande passante.

3) Symptom: Speed caps at ~110 MB/s on “10GbE”

Cause racine : Client/serveur négocié en 1GbE, mauvais hashing LACP, ou contrainte d’un seul flux TCP sans multichannel.

Correctif : Vérifiez la vitesse de lien via ethtool, validez la config du switch, testez SMB multichannel, et confirmez que le client n’est pas sur un segment 1GbE.

4) Symptom: Performance worse after enabling SMB signing or encryption

Cause racine : Bottleneck CPU en crypto/signing, points chauds mono‑thread, queues RSS insuffisantes.

Correctif : Mesurez le CPU par cœur pendant le transfert, activez multiqueue/RSS, upgradez le CPU, ciblez signing/chiffrement, ou utilisez du matériel qui accélère ça.

5) Symptom: Copies intermittently hang for “exactly a few seconds”

Cause racine : Retransmissions réseau, bufferbloat, ou congestion switch ; parfois le timing TXG s’aligne avec les pauses perçues.

Correctif : Regardez les retransmissions (netstat -s), les drops d’interface et les compteurs switch. Si tout est propre, retournez à la latence stockage et au sync.

6) Symptom: One share is slow; another share on same server is fine

Cause racine : Mismatch de propriétés dataset (sync=always, recordsize bizarre, atime activé), différences de config Samba (strict sync, modules VFS), ou quotas/réservations affectant l’allocation.

Correctif : Comparez les sorties zfs get et les blocs testparm -sv pour les deux partages. Normalisez intentionnellement.

7) Symptom: “Windows says it will take 2 hours” but server looks idle

Cause racine : Scan côté client (antivirus, indexation), surcharge petits fichiers, ou client qui attend des opérations metadata par fichier.

Correctif : Reproduisez avec un client propre, testez avec les options robocopy, et confirmez les métriques serveur pendant l’opération. Ne tunez pas les serveurs pour compenser une flotte de endpoints défaillante.

Listes de contrôle / plan pas à pas

Étapes pas à pas : corriger les copies SMB « rapide puis zéro » sur ZFS

  1. Confirmer l’état du pool : zpool status -v. Si dégradé ou erreurs, arrêtez et réparez les disques.
  2. Vérifier le remplissage du pool : zfs list. Si >85% utilisé, planifiez récupération/extension d’espace.
  3. Identifier le dataset et ses propriétés : zfs get recordsize,atime,sync,compression.
  4. Inspecter la config du partage Samba : testparm -sv pour strict sync, paramètres aio, modules VFS.
  5. Mesurer la latence sync : test serveur dd ... conv=fdatasync. Si lent, c’est le principal suspect.
  6. Vérifier la présence/performance du SLOG : zpool status pour logs et assurez‑vous que la classe de device est appropriée.
  7. Observer la latence disque sous charge : iostat -x et zpool iostat pendant la reproduction.
  8. Vérifier la santé réseau : ip -s link, retransmissions (netstat -s), et vitesse lien (ethtool).
  9. Appliquer un changement à la fois : par ex., désactiver strict sync sur un partage test ou ajouter un SLOG ; puis relancer le même transfert et comparer.
  10. Noter le résultat : capturez latence, débit, et si les blocages ont disparu. La mémoire s’efface ; les tickets restent.

Checklist baseline (les trucs ennuyeux que vous remercierez)

  • Scrub hebdomadaire planifié ; rapports de scrub examinés.
  • Snapshot mensuel de : zpool status, propriétés clés zfs get, ip -s link et un test répétable de débit + fsync.
  • Layout des datasets documenté par catégorie de workload.
  • Politique explicite sur le sync : quels partages requièrent des garanties de durabilité.
  • Contrôle des changements pour la config Samba ; pas de « correctifs one‑liner » en production à 2h du matin.

FAQ

1) Pourquoi Explorateur Windows affiche‑t‑il une vitesse rapide, puis 0 B/s ?

Explorateur rapporte sur la base du buffering et de l’acceptation à court terme. ZFS et Samba peuvent accepter les données rapidement, puis caler pendant le commit des écritures sync ou des TXG. Mesurez la latence côté serveur.

2) Robocopy est‑il plus rapide qu’Explorateur ?

Parfois. Le vrai avantage est que robocopy est plus prévisible et scriptable, et qu’il expose les retries et le comportement par fichier. Il ne résoudra pas la latence sync côté serveur.

3) Dois‑je mettre sync=disabled pour accélérer ?

Seulement si vous acceptez de perdre des écritures reconnues en cas de coupure ou crash. Pour des partages temporaires ça peut être acceptable. Pour des données métier, c’est une dégradation de durabilité, pas une astuce de tuning.

4) Ai‑je besoin d’un SLOG pour SMB ?

Si votre workload génère beaucoup d’écritures sync (ou si les paramètres Samba forcent des flushs stricts), un bon SLOG peut être transformateur. Si votre workload est majoritairement async, un SLOG n’aidera pas beaucoup.

5) Quel recordsize utiliser pour les partages SMB ?

Commencez à 128K pour les partages polyvalents. Utilisez 1M pour les archives séquentielles larges. Évitez les changements globaux ; séparez les datasets par workload.

6) Activer LZ4 ralentit‑t‑il ?

Généralement non, et souvent cela accélère en réduisant l’I/O. Si le CPU est déjà saturé (chiffrement/signing, grosse charge), mesurez avant de décider.

7) RAIDZ est‑il mauvais pour SMB ?

Pas « mauvais », mais RAIDZ est moins adapté aux petites écritures aléatoires et aux workloads lourds en métadonnées que les mirrors. Si votre cas SMB implique beaucoup de petits fichiers et un comportement sync, les mirrors gagnent souvent en latence.

8) Pourquoi un partage SMB est‑il lent alors qu’un autre sur le même serveur va bien ?

Propriétés dataset différentes ou options de partage Samba. Cherchez sync=always, atime=on, recordsize étrange ou strict sync activé uniquement sur un partage.

9) SMB Multichannel règle‑t‑il tout ?

Non. Il peut augmenter le débit et la résilience, mais ne corrigera pas la latence stockage ou les stalls sync. Il nécessite aussi un support NIC, driver et client correct.

10) Comment savoir si c’est CPU‑bound ?

Pendant le transfert, un ou plusieurs cœurs seront constamment chargés, souvent dans smbd ou le réseau/kernel crypto. Pendant ce temps les disques et les NICs ne seront pas saturés. C’est votre signal.

Étapes suivantes réalisables cette semaine

Faites‑les dans l’ordre. Chaque étape clarifie une décision, et aucune ne requiert la foi.

  1. Choisissez un test de transfert reproductible (un gros fichier et un dossier « beaucoup de petits fichiers ») et gardez‑le constant.
  2. Exécutez la feuille de route de diagnostic rapide et capturez les sorties : zpool iostat, iostat -x, ip -s link, netstat -s, smbstatus.
  3. Prouvez ou éliminez la latence sync avec le test serveur dd ... conv=fdatasync sur le dataset.
  4. Séparez les datasets par workload si ce n’est pas fait. Mettez atime=off et des recordsize sensés par catégorie.
  5. Corrigez le vrai goulot : ajoutez un SLOG adapté pour les partages sync‑heavy, libérez de l’espace si le pool est trop plein, ou traitez CPU/réseau si c’est là que les preuves pointent.
  6. Rédigez un runbook d’une page avec vos commandes de base et les sorties « normales ». Le futur vous achètera un café au passé vous.

L’objectif n’est pas un graphe parfait. L’objectif est une performance prévisible sous le contrat de durabilité que vous voulez réellement offrir. Une fois que vous choisissez ce contrat volontairement, ZFS et SMB cessent d’être mystérieux et deviennent… simplement exigeants.

Docker : Sauvegardes que vous n’avez jamais testées — Comment exécuter correctement un exercice de restauration

Vous avez des sauvegardes. Vous avez même une coche verte dans un tableau de bord. Puis un nœud meurt, l’astreinte lance une restauration,
et soudain la seule chose que vous restaurez, c’est votre respect pour la loi de Murphy.

Docker facilite le déploiement d’applications. Il facilite aussi le fait d’oublier où les données vivent réellement : volumes, montages bind,
secrets, fichiers d’environnement, registres et quelques répertoires « temporaires » qu’un collègue a codés en dur à 2 h du matin.

Un exercice de restauration est un produit, pas un rituel

Une « sauvegarde » est une promesse. Un exercice de restauration est l’endroit où vous remboursez la promesse et prouvez que vous pouvez la tenir sous pression.
Le livrable n’est pas un tarball dans un stockage objet. C’est un processus de récupération répétable avec des bornes temporelles connues.

L’exercice de restauration a un seul rôle : convertir des hypothèses en mesures. Quel est votre RPO (combien de données vous pouvez perdre)
et votre RTO (combien de temps vous pouvez être indisponible) ? Quelles parties sont lentes ? Quelles parties sont fragiles ? Quelles parties nécessitent la mémoire
et la caféine d’une personne spécifique ?

Le résultat le plus précieux d’un exercice est souvent ennuyeux : une liste de fichiers manquants, de permissions incorrectes, de secrets introuvables,
et de surprises du type « on croyait que c’était dans la sauvegarde ». L’ennuyeux, c’est bon. L’ennuyeux, c’est comment vous survivez aux incidents.

Une citation à garder sur votre bureau : L’espoir n’est pas une stratégie. (attribuée au général Gordon R. Sullivan)

Ce que vous restaurez réellement dans Docker

Docker ne « contient » pas l’état. Il rend simplement l’état plus facile à égarer. Pour les exercices de restauration, traitez votre système comme des couches :
état de l’hôte, état des conteneurs, état des données et état de déploiement. Ensuite décidez ce que vous promettez de restaurer.

1) État des données

  • Volumes nommés (gérés par Docker) : généralement sous /var/lib/docker/volumes.
  • Montages bind : n’importe où dans le système de fichiers de l’hôte ; souvent pas dans la même politique de sauvegarde que les volumes.
  • Stockage externe : NFS, iSCSI, Ceph, EBS, LUN SAN, datasets ZFS, LVM, etc.
  • Bases de données : Postgres/MySQL/Redis/Elastic/etc. La méthode de sauvegarde compte plus que l’endroit où elle est stockée.

2) État de déploiement

  • Fichiers Compose, fichiers d’environnement et overrides.
  • Secrets et leur mécanisme de distribution (secrets Swarm, fichiers, SOPS, modèles Vault, etc.).
  • Tags d’images : « latest » n’est pas un plan de restauration.
  • Accès au registre : si vous ne pouvez pas pull, vous ne pouvez pas démarrer.

3) État de l’hôte

  • Configuration de Docker Engine, driver de stockage, flags du démon.
  • Noyau + détails du système de fichiers : attentes overlay2, xfs ftype, SELinux/AppArmor.
  • Réseau : règles de firewall, DNS, routes, MTU.

4) État d’exécution (généralement pas utile à « restaurer »)

Les couches de conteneur et les fichiers d’exécution éphémères peuvent être recréés. Si vous sauvegardez tout le répertoire Docker root
(/var/lib/docker) en espérant ressusciter les conteneurs octet par octet, vous vous engagez à rencontrer des cassures subtiles.
La cible correcte est presque toujours les volumes de données plus la configuration de déploiement, et reconstruire proprement les conteneurs.

Blague n°1 : Si votre plan de récupération commence par « je crois que les données sont sur ce nœud-là », félicitations — vous avez inventé un point unique de surprise.

Faits & contexte historique (pour arrêter de répéter les mêmes erreurs)

  • Fait 1 : L’époque AUFS de Docker a normalisé l’idée que les conteneurs sont jetables ; beaucoup d’équipes ont par erreur rendu les données jetables aussi.
  • Fait 2 : Le passage d’AUFS à overlay2 n’était pas qu’une question de performances : les sémantiques de restauration et les exigences du système de fichiers ont changé (notamment l’attente XFS ftype=1).
  • Fait 3 : Le mouvement vers une « infrastructure immuable » a réduit les restaurations d’hôtes mais augmenté le besoin de restaurer l’état externalisé (volumes, stockages objets, bases gérées).
  • Fait 4 : Compose est devenu la description d’application par défaut pour beaucoup d’organisations, même lorsque la rigueur opérationnelle (rotation des secrets, versions figées, healthchecks) n’a pas suivi.
  • Fait 5 : Beaucoup d’incidents imputés à « Docker » sont en réalité des problèmes de cohérence de stockage : copies de systèmes de fichiers prises sous une base de données active.
  • Fait 6 : Le ransomware a fait évoluer la stratégie de sauvegarde de « peut-on restaurer ?» à « peut-on restaurer sans faire confiance à l’attaquant qui aurait chiffré nos clés de sauvegarde ? »
  • Fait 7 : Les registres d’images conteneurisées sont devenus une infrastructure critique ; perdre un registre privé ou ses identifiants peut bloquer les restaurations même si les données sont saines.
  • Fait 8 : Les snapshots de systèmes de fichiers (LVM/ZFS) ont facilité les sauvegardes rapides — mais ils ont aussi encouragé l’excès de confiance quand les applications n’étaient pas sûres pour les snapshots.
  • Fait 9 : L’essor des conteneurs sans privilèges rootless a changé les chemins de sauvegarde et les modèles de permissions ; restaurer des données en root peut discrètement casser des runtimes rootless par la suite.

Choisir la portée de l’exercice : hôte, application ou couche données

Un exercice de restauration peut être trois choses différentes. Si vous ne déclarez pas laquelle vous faites, vous « réussirez » à la plus facile
et échouerez à celle qui importe.

Portée A : Exercice de restauration des données (le plus courant, le plus utile)

Vous restaurez les données des volumes/montages bind et redéployez les conteneurs depuis des images et configurations connues. C’est la bonne valeur par défaut
pour la plupart des déploiements Docker Compose en production.

Portée B : Exercice de restauration de l’application (déploiement + données)

Vous restaurez la pile d’applications exacte : fichiers Compose, env/secrets, reverse proxy, certificats, plus les données. Cela valide l’hypothèse « tout ce qu’il faut pour faire tourner ».
Cela expose aussi la maladie du « on gardait cette conf sur l’ordinateur portable de quelqu’un ».

Portée C : Recréation de l’hôte (rare, mais à faire au moins annuellement)

Vous supposez que le nœud est perdu. Vous fournissez un hôte neuf et restaurez dessus. C’est là que vous découvrez la dépendance à des noyaux anciens, des paquets manquants,
des règles iptables personnalisées, d’étranges hacks MTU et des incompatibilités de drivers de stockage.

Mode opératoire de diagnostic rapide (trouver le goulot vite)

Pendant une restauration, vous êtes typiquement bloqué par l’un des quatre éléments : identité/identifiants, intégrité des données,
vitesse de transfert des données, ou correction de l’application. Ne devinez pas. Triez dans cet ordre.

Premier : Pouvez-vous même accéder à ce dont vous avez besoin ?

  • Avez-vous les identifiants du dépôt de sauvegarde et les clés de chiffrement ?
  • L’hôte de restauration peut-il atteindre le stockage objet / serveur de sauvegarde / NAS ?
  • Pouvez-vous puller les images conteneurs (ou avez-vous un cache air‑gapped) ?

Second : La sauvegarde est-elle complète et cohérente en interne ?

  • Avez-vous tous les chemins attendus de volumes/montages bind pour l’application ?
  • Les sommes de contrôle correspondent-elles ? Pouvez-vous lister et extraire les fichiers ?
  • Pour les bases : avez-vous un backup logique ou seulement une copie crash‑consistante du système de fichiers ?

Troisième : Où passe le temps ?

  • Débit réseau (egress du stockage objet, contraintes VPN, throttling) ?
  • Décompression et crypto (outils de restauration mono‑thread) ?
  • IOPS et tempêtes de petits fichiers (millions de fichiers minuscules) ?

Quatrième : Pourquoi l’application ne démarre-t-elle pas ?

  • Permissions/possession/labels SELinux sur les données restaurées.
  • Dérive de configuration : variables d’environnement, secrets, tags d’images modifiés.
  • Incompatibilité de schéma : restaurer des données anciennes dans une nouvelle version d’application.

Si vous ne retenez qu’une chose : mesurez la vitesse de transfert et vérifiez les clés tôt. Tout le reste est secondaire.

Construire un environnement de restauration réaliste

Un exercice de restauration sur le même hôte qui a produit les sauvegardes est un mensonge réconfortant. Il partage les mêmes images en cache,
les mêmes identifiants déjà connectés et les mêmes règles de firewall ajustées manuellement. Votre objectif est d’échouer honnêtement.

Ce que « réaliste » veut dire

  • Hôte neuf : nouvelle VM ou bare metal, même famille d’OS, mêmes versions majeures.
  • Même contraintes réseau : même route vers le stockage de sauvegarde, même NAT/VPN, même DNS.
  • Pas d’état caché : ne réutilisez pas l’ancien /var/lib/docker ; ne montez pas directement des volumes de production.
  • Limité dans le temps : vous testez le RTO ; arrêtez d’admirer les logs et démarrez un chronomètre.

Définir les critères de réussite au départ

  • RPO validé : vous pouvez pointer sur la sauvegarde la plus récente et en montrer l’horodatage et le contenu.
  • RTO mesuré : de « hôte provisionné » à « service répond correctement ».
  • Correction vérifiée : pas seulement « les conteneurs tournent » mais « les données sont correctes ».

Tâches pratiques : commandes, sorties, décisions

Voici des tâches d’exercice de restauration que j’attends dans un runbook. Chacune inclut une commande, ce que signifie la sortie et
la décision que vous en tirez. Exécutez‑les sur l’hôte cible de restauration sauf indication contraire.

Task 1: Inventory running containers and their mounts (source environment)

cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
NAMES               IMAGE                         STATUS
api                 registry.local/api:1.42.0     Up 3 days
postgres            postgres:15                   Up 3 days
nginx               nginx:1.25                    Up 3 days

Signification : Ceci est la liste minimale « ce qui existe ». Ce n’est pas suffisant, mais c’est un début.
Décision : Identifiez quels conteneurs sont stateful (ici : postgres) et lesquels sont stateless.

cr0x@server:~$ docker inspect postgres --format '{{range .Mounts}}{{.Type}} {{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'
volume pgdata -> /var/lib/postgresql/data
bind /srv/postgres/conf -> /etc/postgresql

Signification : Vous avez à la fois un volume nommé et un montage bind. Deux politiques de sauvegarde, deux modes de défaillance.
Décision : Votre plan de restauration doit capturer à la fois pgdata et /srv/postgres/conf.

Task 2: List Docker volumes and map them to projects

cr0x@server:~$ docker volume ls
DRIVER    VOLUME NAME
local     myapp_pgdata
local     myapp_redisdata
local     shared_uploads

Signification : Les noms de volumes encodent souvent les noms de projet Compose. C’est utile lors des restaurations.
Décision : Décidez quels volumes sont critiques et lesquels peuvent être reconstruits (par ex. caches).

Task 3: Identify where volumes live on disk (restore host)

cr0x@server:~$ docker info --format '{{.DockerRootDir}}'
/var/lib/docker

Signification : Répertoire Docker root par défaut. Les volumes seront sous ce chemin sauf configuration différente.
Décision : Confirmez que cela correspond à vos attentes de sauvegarde ; les divergences provoquent des « restauration réussie, données manquantes. »

Task 4: Verify filesystem and free space before restoring

cr0x@server:~$ df -hT /var/lib/docker /srv
Filesystem     Type   Size  Used Avail Use% Mounted on
/dev/sda2      ext4   200G   32G  158G  17% /
/dev/sdb1      xfs    800G  120G  680G  15% /srv

Signification : Vous avez de la marge de capacité. Notez aussi les types de système de fichiers ; certains comportements diffèrent pour overlay et permissions.
Décision : Si l’espace disponible est faible, vous n’« essayez pas quand même ». Redimensionnez d’abord ou choisissez une cible de restauration plus grande.

Task 5: Confirm Docker storage driver and kernel compatibility

cr0x@server:~$ docker info --format 'Driver={{.Driver}}; BackingFS={{.BackingFilesystem}}'
Driver=overlay2; BackingFS=extfs

Signification : overlay2 sur ext4 (Docker rapporte « extfs »). Si votre hôte d’origine utilisait un driver différent, n’assumez pas la portabilité de /var/lib/docker.
Décision : Préférez restaurer uniquement les volumes et la config ; reconstruisez les conteneurs depuis les images.

Task 6: Verify the backup artifact exists and is recent

cr0x@server:~$ ls -lh /backups/myapp/
total 4.1G
-rw------- 1 root root 1.9G Jan  2 01:05 myapp-volumes-2026-01-02.tar.zst
-rw------- 1 root root 2.2G Jan  2 01:06 myapp-bindmounts-2026-01-02.tar.zst
-rw------- 1 root root  12K Jan  2 01:06 myapp-compose-2026-01-02.tgz

Signification : Artifacts séparés pour volumes, bind mounts et config de déploiement : sain. Cela rend les restaurations partielles possibles.
Décision : Si le fichier le plus récent est plus ancien que votre RPO, arrêtez et escaladez. Restaurer des données périmées sans prévenir est la voie royale vers un incident.

Task 7: Validate archive integrity before extraction

cr0x@server:~$ zstd -t /backups/myapp/myapp-volumes-2026-01-02.tar.zst
/backups/myapp/myapp-volumes-2026-01-02.tar.zst: OK

Signification : Le flux compressé n’est pas corrompu.
Décision : Si cela échoue, n’extrayez pas « une partie ». Trouvez un autre jeu de sauvegarde ou refaites la chaîne de sauvegarde.

Task 8: Dry-run list of files inside the backup (spot missing paths)

cr0x@server:~$ tar -I zstd -tf /backups/myapp/myapp-bindmounts-2026-01-02.tar.zst | head
srv/postgres/conf/postgresql.conf
srv/postgres/conf/pg_hba.conf
srv/myapp/env/.env.production
srv/nginx/conf.d/app.conf

Signification : Vous pouvez voir les fichiers de configuration attendus pour les montages bind.
Décision : Si des répertoires clés sont manquants, arrêtez et corrigez la définition de sauvegarde. Les exercices de restauration ne sont pas des tours de magie.

Task 9: Restore bind mounts to a staging prefix first (avoid clobbering)

cr0x@server:~$ mkdir -p /restore-staging
cr0x@server:~$ tar -I zstd -xpf /backups/myapp/myapp-bindmounts-2026-01-02.tar.zst -C /restore-staging
cr0x@server:~$ ls -la /restore-staging/srv/postgres/conf
total 24
drwxr-xr-x 2 root root 4096 Jan  2 01:06 .
drwxr-xr-x 3 root root 4096 Jan  2 01:06 ..
-rw-r--r-- 1 root root  980 Jan  2 01:06 pg_hba.conf
-rw-r--r-- 1 root root 3150 Jan  2 01:06 postgresql.conf

Signification : Fichiers restaurés avec permissions préservées (-p). La propriété sera importante ensuite.
Décision : Comparez le staging à la mise en place cible. Ce n’est qu’ensuite que vous déplacerez en place.

Task 10: Restore named volume data using a helper container

Pour les volumes nommés, ne copiez pas manuellement dans les entrailles de Docker. Utilisez un conteneur temporaire qui monte le volume.

cr0x@server:~$ docker volume create myapp_pgdata
myapp_pgdata
cr0x@server:~$ docker run --rm -v myapp_pgdata:/data -v /backups/myapp:/backup alpine:3.20 sh -c "cd /data && tar -I zstd -xpf /backup/myapp-volumes-2026-01-02.tar.zst --strip-components=2 ./volumes/myapp_pgdata"
tar: removing leading './' from member names

Signification : Vous extrayez seulement le sous‑arbre pour ce volume dans le chemin monté du volume.
Décision : Si la disposition de l’archive ne correspond pas à vos attentes, arrêtez et revérifiez le script de sauvegarde ; n’improvisez pas des restaurations partielles.

Task 11: Sanity-check restored volume contents and ownership

cr0x@server:~$ docker run --rm -v myapp_pgdata:/data alpine:3.20 sh -c "ls -la /data | head"
total 128
drwx------    19 999      999           4096 Jan  2 01:04 .
drwxr-xr-x     1 root     root          4096 Jan  2 02:10 ..
-rw-------     1 999      999              3 Jan  2 01:04 PG_VERSION
drwx------     5 999      999           4096 Jan  2 01:04 base

Signification : La propriété est 999:999, typique de l’image officielle Postgres. Bien.
Décision : Si la propriété est incorrecte (par ex. root), corrigez‑la maintenant (chown) ou Postgres peut refuser de démarrer.

Task 12: Restore deployment config and pin image versions

cr0x@server:~$ mkdir -p /opt/myapp
cr0x@server:~$ tar -xpf /backups/myapp/myapp-compose-2026-01-02.tgz -C /opt/myapp
cr0x@server:~$ ls -la /opt/myapp
total 40
drwxr-xr-x 3 root root 4096 Jan  2 02:13 .
drwxr-xr-x 3 root root 4096 Jan  2 02:13 ..
-rw-r--r-- 1 root root 2241 Jan  2 01:06 docker-compose.yml
-rw------- 1 root root  412 Jan  2 01:06 .env.production

Signification : La configuration est présente, y compris le fichier env. Traitez‑la comme sensible.
Décision : Assurez‑vous que les images sont figées sur des tags ou des digests de confiance. Si le fichier Compose utilise latest, corrigez‑le dans le cadre de l’exercice.

Task 13: Validate images can be pulled (or are already available)

cr0x@server:~$ docker compose -f /opt/myapp/docker-compose.yml pull
[+] Pulling 3/3
 ✔ postgres Pulled
 ✔ api      Pulled
 ✔ nginx    Pulled

Signification : Votre chemin de registre, vos identifiants et le réseau fonctionnent.
Décision : Si les pulls échouent, votre plan de restauration doit inclure un miroir de registre, des tarballs d’images hors‑ligne ou des étapes de récupération d’identifiants.

Task 14: Bring the stack up and watch for fast failures

cr0x@server:~$ docker compose -f /opt/myapp/docker-compose.yml up -d
[+] Running 3/3
 ✔ Container myapp-postgres-1  Started
 ✔ Container myapp-api-1       Started
 ✔ Container myapp-nginx-1     Started

Signification : Les conteneurs ont démarré, mais ce n’est pas une preuve de correction.
Décision : Vérifiez immédiatement les logs et les endpoints de santé. « Démarré » peut encore vouloir dire « cassé silencieusement ».

Task 15: Verify health and read the first errors, not the last ones

cr0x@server:~$ docker compose -f /opt/myapp/docker-compose.yml ps
NAME                IMAGE                         COMMAND                  SERVICE    STATUS          PORTS
myapp-api-1          registry.local/api:1.42.0     "gunicorn -c ..."        api        Up 20s (healthy) 0.0.0.0:8080->8080/tcp
myapp-nginx-1        nginx:1.25                    "/docker-entrypoint…"    nginx      Up 20s          0.0.0.0:80->80/tcp
myapp-postgres-1     postgres:15                   "docker-entrypoint…"     postgres   Up 20s (healthy) 5432/tcp

Signification : Les healthchecks passent. C’est un signal fort, pas une garantie.
Décision : Si la santé échoue, vérifiez les logs pertinents les plus anciens (DB d’abord, puis l’app, puis le proxy).

cr0x@server:~$ docker logs --tail=50 myapp-postgres-1
2026-01-02 02:14:12.101 UTC [1] LOG:  database system is ready to accept connections

Signification : Postgres est prêt à accepter des connexions. Si vous voyez « invalid checkpoint record » ou « permission denied », votre restauration n’est pas correcte.
Décision : Pour les erreurs DB, décidez si vous avez besoin d’une restauration logique plutôt qu’une copie du système de fichiers.

Task 16: Prove correctness with an application-level query

cr0x@server:~$ curl -fsS http://127.0.0.1:8080/health
{"status":"ok","db":"ok","version":"1.42.0"}

Signification : Votre application déclare qu’elle est saine. Validez maintenant les données, pas seulement la vivacité.
Décision : Exécutez une requête connue ou un contrôle métier (par ex. « un client spécifique existe »).

cr0x@server:~$ docker exec -i myapp-postgres-1 psql -U postgres -tAc "select now(), count(*) from users;"
2026-01-02 02:14:35.812396+00|1842

Signification : Vous avez des données, et elles semblent plausibles.
Décision : Comparez avec une plage attendue ou un rapport de checksum. Si le compte est zéro, vous avez restauré le mauvais élément ou pointé vers un volume vide.

Task 17: Measure restore throughput to predict RTO

cr0x@server:~$ /usr/bin/time -f 'elapsed=%E cpu=%P' tar -I zstd -xpf /backups/myapp/myapp-volumes-2026-01-02.tar.zst -C /restore-staging-voltest
elapsed=0:02:41 cpu=380%

Signification : Le CPU est un goulot (380% indique ~4 cœurs occupés). Bien à savoir.
Décision : Si la restauration est liée au CPU, vous pouvez réduire le RTO en ajoutant des cœurs, en ajustant le niveau de compression ou en changeant d’outil — avant le vrai incident.

Task 18: Verify file permissions and SELinux/AppArmor context (where applicable)

cr0x@server:~$ getenforce
Enforcing

Signification : SELinux est activé. Les montages bind peuvent nécessiter des labels, sinon les conteneurs auront « permission denied » malgré des perms UNIX corrects.
Décision : Si vous utilisez SELinux, assurez‑vous que votre Compose utilise :z ou :Z là où il faut, et relabellez les chemins restaurés.

cr0x@server:~$ ls -lZ /srv/postgres/conf | head -n 3
-rw-r--r--. root root unconfined_u:object_r:default_t:s0 pg_hba.conf
-rw-r--r--. root root unconfined_u:object_r:default_t:s0 postgresql.conf

Signification : Le type SELinux par défaut sur les fichiers de conf peut ne pas correspondre à ce que votre conteneur attend.
Décision : Si les conteneurs ne peuvent pas lire les montages bind sous SELinux, vous devez relabeler ou ajuster les options de montage ; ne désactivez pas SELinux comme « solution ».

Task 19: Confirm the restore didn’t silently swap volumes

cr0x@server:~$ docker inspect myapp-postgres-1 --format '{{range .Mounts}}{{.Name}} {{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'
myapp_pgdata /var/lib/docker/volumes/myapp_pgdata/_data -> /var/lib/postgresql/data

Signification : Le conteneur utilise le volume attendu.
Décision : Si vous voyez un nom de volume inattendu, vous restaurez dans un volume mais vous exécutez contre un autre. C’est une erreur classique d’exercice.

Task 20: Capture evidence and timings (so the drill improves)

cr0x@server:~$ journalctl -u docker --since "today" | tail -n 5
Jan 02 02:13:55 server dockerd[1023]: API listen on /run/docker.sock
Jan 02 02:14:03 server dockerd[1023]: Loading containers: done.

Signification : Vous avez des horodatages pour le démarrage du démon Docker et le chargement des conteneurs.
Décision : Enregistrez-les dans le rapport d’exercice avec les heures de début/fin. Si vous ne mesurez pas, vous vous disputerez pendant l’incident au lieu d’agir.

Blague n°2 : Un exercice de restauration, c’est comme passer la soie dentaire — tout le monde prétend le faire, et les preuves saignent généralement.

Trois mini-histoires d’entreprise (comment ça échoue dans la réalité)

Mini‑histoire 1 : L’incident causé par une mauvaise hypothèse

Une entreprise SaaS de taille moyenne utilisait Docker Compose sur quelques VM costauds. Leurs sauvegardes étaient « simples » : un tar nocturne de
/srv plus un snapshot hebdomadaire du disque de la VM. L’hypothèse était que tout d’important vivait dans /srv.

L’incident commença par une défaillance de stockage banale. La VM ne démarrait plus proprement après un incident d’hôte. L’équipe provisionna
une nouvelle VM et restaura /srv depuis la sauvegarde nocturne. Compose démarra. Nginx servait les pages. L’API renvoyait des 500.

Les logs Postgres montraient un nouveau cluster de base initialisé vide. Personne ne l’avait restauré — parce que personne ne l’avait sauvegardé. La DB utilisait
un volume Docker nommé, situé dans la racine Docker sous /var/lib/docker/volumes, en dehors du périmètre de sauvegarde. Le snapshot hebdomadaire de la VM le contenait,
mais il était trop ancien pour le RPO implicite de l’entreprise, et il était géré par une autre équipe.

Le postmortem n’était pas spectaculaire. C’était pire : c’était évident. Ils avaient confondu « le répertoire de données de notre appli » avec « l’endroit où Docker stocke l’état ».
La correction n’était pas sophistiquée non plus : inventorier les montages, sauvegarder explicitement les volumes nommés et exécuter un exercice trimestriel sur un hôte propre.
Aussi : arrêtez d’appeler « sauvegardes simples » ce qui n’inclut pas votre base de données.

Mini‑histoire 2 : L’optimisation qui s’est retournée contre eux

Une autre organisation se mit sérieusement à optimiser la vitesse. Leur temps de restauration était trop lent pour la patience de la direction, alors ils optimisèrent.
Ils passèrent des dumps logiques de BD aux snapshots crash‑consistants du volume de base de données. C’était plus rapide et produisait des transferts incrémentaux plus petits. Tout le monde était content.

Six mois plus tard, ils eurent besoin de restaurer. Un mauvais déploiement corrompit l’état applicatif et ils roulèrent en arrière. La restauration « fonctionnait »
mécaniquement : le snapshot s’extrayait, les conteneurs démarraient, les healthchecks devenaient verts. Puis le trafic monta, et la BD commença à lancer des erreurs :
corruption subtile d’index, bizarreries du planificateur de requêtes, puis boucle de crash.

La cause racine était plate mais mortelle : le snapshot avait été pris alors que la base subissait des écritures, sans coordonner un checkpoint ni utiliser un mécanisme de backup natif DB.
La sauvegarde du volume était cohérente au niveau système de fichiers, pas nécessairement au niveau base de données. Elle a restauré vite et échoué tard — exactement le genre d’échec qui fait perdre le plus de temps.

La correction fut un compromis : conserver des snapshots rapides pour les récupérations « oups » à court terme, mais aussi prendre périodiquement des backups natifs DB (ou exécuter la procédure de base backup supportée)
pouvant être validés. Ils ajoutèrent aussi un job de vérification qui démarre une DB restaurée en bac à sable et exécute des checks d’intégrité. Les optimisations sont permises. Les optimisations non vérifiées sont juste des risques déguisés en performance.

Mini‑histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise

Une entreprise proche des finances faisait tourner plusieurs services clients dans Docker. Leur lead SRE n’était pas romantique. Tous les trimestres, ils exécutaient
un exercice de restauration utilisant un VPC isolé, une image VM propre et une copie du dépôt de sauvegarde. L’exercice avait une checklist et un chronomètre.

L’exercice incluait toujours les mêmes étapes fastidieuses : vérifier que les clés de chiffrement sont accessibles à l’astreinte, valider les manifests de sauvegarde, restaurer les volumes
en staging d’abord, puis les remplacer en place, puis exécuter une série de contrôles de sanity applicatifs. Enfin, documenter les temps et mettre à jour le runbook.
Personne n’aimait ça. Personne ne le mettait sur une diapositive.

Puis un incident réel arriva : une erreur opérateur effaça un volume de production et la réplication se fit rapidement. L’astreinte suivit le runbook sans improviser.
Ils savaient déjà que l’étape la plus lente était la décompression et avaient ajusté la taille de l’hôte de restauration pour cela. Ils savaient déjà exactement quels secrets
devaient être présents et où ils étaient. Ils avaient déjà traité le problème d’étiquetage SELinux — dans l’exercice, pas pendant l’incident.

La restauration se termina dans la fenêtre attendue. Pas parce que l’équipe fut héroïque, mais parce qu’elle avait choisi l’ennui volontairement. En exploitation, l’ennuyeux est une fonctionnalité.

Listes de contrôle / plan étape par étape

Plan d’exercice de restauration (répétable, pas « voyons ce qui se passe »)

  1. Déclarez la portée et les critères de réussite.

    • Quels services ? Quels jeux de données ? Quels RPO/RTO validez‑vous ?
    • Que signifie « correct » (requêtes, checksums, actions UI, compteurs de messages) ?
  2. Gelez l’inventaire.

    • Exportez les fichiers Compose et les références env/secrets.
    • Listez les volumes et montages bind par conteneur.
    • Enregistrez les références d’images (tags ou digests).
  3. Provisionnez une cible de restauration fraîche.

    • Même famille d’OS, CPU/mémoire similaires, mêmes choix de système de fichiers.
    • Même chemin réseau vers les sauvegardes et registres (ou explicitement différent si vous testez une région DR).
  4. Récupérez les artifacts de sauvegarde et validez l’intégrité.

    • Checksum, déchiffrez, listez le contenu, vérifiez les horodatages.
    • Confirmez que vous avez les clés et mots de passe dans le modèle d’accès attendu pendant un incident.
  5. Restaurez d’abord en staging.

    • Montages bind dans /restore-staging.
    • Volumes via conteneurs helpers dans des volumes nouvellement créés.
  6. Appliquez permissions, labels et propriétés.

    • Les volumes DB doivent correspondre aux attentes UID/GID du conteneur.
    • SELinux/AppArmor : assurez les labels et options de montage corrects.
  7. Démarrez la stack avec des images connues bonnes.

    • Pull des images ; si le pull échoue, utilisez des images en cache/hors‑ligne.
    • Démarrez DB d’abord, puis l’app, puis les proxies edge.
  8. Vérifiez la correction.

    • Endpoint de santé + au moins une requête de données par service critique.
    • Pour files/caches : vérifiez que vous n’avez pas besoin de les restaurer (souvent non).
  9. Mesurez les temps et rédigez le rapport d’exercice.

    • Début/fin de restauration, débit de transfert, goulots, échecs, corrections.
    • Mettez à jour le runbook et automatisez les étapes fragiles.

Ce qu’il faut automatiser après votre premier exercice honnête

  • Export d’inventaire : montages, volumes, images, configs Compose.
  • Génération de manifestes de sauvegarde : chemins et volumes attendus, tailles, horodatages.
  • Vérifications d’intégrité : checksums, tests d’archive, restauration périodique en sandbox.
  • Normalisation des permissions : mapping UID/GID connu par service.
  • Rétention d’images : conservez les images requises pour votre fenêtre RPO (ou exportez des tars).

Erreurs courantes : symptôme → cause racine → correction

1) « Les conteneurs sont démarrés, mais l’application est vide »

Symptôme : Les healthchecks passent, mais les données utilisateurs sont manquantes ou réinitialisées aux valeurs par défaut.
Cause racine : Restauration dans le mauvais nom de volume, ou Compose a créé un nouveau volume vide à cause d’un mismatch de nom de projet.
Correction : Inspectez les montages (docker inspect), assurez‑vous que les noms de volumes correspondent, et nommez explicitement les volumes dans Compose plutôt que de compter sur le scope implicite du projet.

2) « Permission denied » sur des montages bind restaurés

Symptôme : Les conteneurs plantent avec des erreurs d’accès aux fichiers ; les fichiers semblent corrects sur l’hôte.
Cause racine : Labels SELinux incorrects, ou un conteneur rootless attend une propriété différente de celle produite par la restauration.
Correction : Utilisez les options de montage :z/:Z

3) Postgres/MySQL démarre puis se comporte étrangement sous charge

Symptôme : La BD démarre, puis vous voyez des erreurs de type corruption ou des crashes plus tard.
Cause racine : Sauvegarde crash‑consistante prise sans coordination avec la BD ; état WAL/checkpoint incohérent.
Correction : Préférez les méthodes de backup natives de la BD pour des restaurations durables ; si vous utilisez des snapshots, coordonnez‑les avec le mode de backup supporté par la BD et validez dans un sandbox.

4) La restauration est « lente sans raison »

Symptôme : Heures de restauration, CPU saturé, disques sous‑utilisés.
Cause racine : Décompression/chiffrement mono‑thread ou niveau de compression trop élevé ; millions de petits fichiers amplifiant les opérations métadonnées.
Correction : Benchmarquez la décompression, envisagez une compression moins lourde ou des outils parallèles, et restructurez les sauvegardes (par ex. archives par volume) pour réduire la thrash métadonnées.

5) Vous ne pouvez pas puller les images pendant la restauration

Symptôme : Auth registre échoue, DNS échoue, ou les images ont disparu.
Cause racine : Identifiants stockés uniquement sur l’ancien hôte ; rétention du registre ayant garbage‑collecté des tags dont vous dépendez ; dépendance aux limites de rate des registres publics.
Correction : Stockez les identifiants de registre dans un gestionnaire de secrets récupérable, figez par digest ou tags immutables, et conservez un cache/offline des images critiques.

6) Compose « marche en prod » mais échoue sur l’hôte de restauration

Symptôme : Même fichier Compose, comportement différent : ports, DNS, réseaux, problèmes MTU.
Cause racine : Configuration hôte cachée et divergente : sysctls, iptables, modules noyau, daemon.json personnalisé, ou réseau cloud spécifique.
Correction : Codifiez le provisionnement hôte (IaC), exportez et versionnez les réglages du démon, et incluez un exercice de restauration sur hôte neuf annuellement.

7) La sauvegarde est présente, mais pas les clés

Symptôme : Vous voyez le fichier de sauvegarde mais ne pouvez pas le déchiffrer ou y accéder pendant la réponse à l’incident.
Cause racine : Clés/mots de passe verrouillés derrière une personne, un laptop mort, ou un chemin SSO cassé.
Correction : Entraînez la récupération de clés pendant les exercices, stockez l’accès break‑glass correctement, et vérifiez la procédure avec un rôle d’astreinte à moindre privilège.

8) Vous avez restauré la config mais pas les dépendances ennuyeuses

Symptôme : L’app démarre mais ne peut pas envoyer d’e-mails, atteindre le prestataire de paiement, ou les callbacks échouent.
Cause racine : Certificats TLS manquants, règles de firewall, enregistrements DNS, secrets webhook, ou listes d’autorisation sortantes.
Correction : Traitez les dépendances externes comme partie de l’« état de déploiement » et testez‑les dans l’exercice (ou simulez explicitement et documentez).

FAQ

1) Dois‑je sauvegarder /var/lib/docker ?

Généralement non. Sauvegardez les volumes et tous les répertoires applicatifs montés en bind, plus la configuration Compose et les références aux secrets.
Sauvegarder tout le répertoire Docker root est fragile entre versions, drivers de stockage et différences d’hôte.

2) Quelle est la façon la plus sûre de sauvegarder une base de données dans Docker ?

Utilisez le mécanisme de sauvegarde supporté par la base (dumps logiques, base backups, archivage WAL, etc.) et validez en restaurant dans un sandbox.
Les backups au niveau système de fichiers peuvent fonctionner s’ils sont correctement coordonnés, mais « ça avait l’air OK une fois » n’est pas une méthode.

3) À quelle fréquence devrais‑je exécuter des exercices de restauration ?

Trimestriels pour les systèmes critiques est un bon seuil. Mensuels si le système change constamment ou si RTO/RPO sont serrés.
Exécutez aussi un exercice après des changements majeurs : migration de stockage, mise à jour de Docker, mise à jour de la DB, ou changement d’outil de sauvegarde.

4) Puis‑je faire un exercice de restauration sans dupliquer les données de production (problèmes de vie privée) ?

Oui : utilisez des jeux de données masqués, des fixtures synthétiques, ou restaurez dans un environnement isolé chiffré avec contrôles d’accès stricts.
Mais vous devez quand même restaurer une structure réaliste : permissions, tailles, nombre de fichiers, schéma et comportement d’exécution.

5) Quelle est la chose n°1 qui fait exploser le temps de restauration ?

Les petits fichiers et les arbres à métadonnées lourdes, surtout combinés avec chiffrement et compression. Vous pouvez avoir beaucoup de bande passante
et être bloqué par le CPU ou les IOPS.

6) Dois‑je compresser les sauvegardes ?

Généralement oui, mais choisissez une compression adaptée à vos contraintes de restauration. Si vous êtes lié au CPU pendant la restauration, une forte compression
nuit au RTO. Mesurez‑le avec une extraction chronométrée pendant les exercices et ajustez.

7) Comment savoir si j’ai restauré la bonne chose ?

Ne faites pas confiance au statut des conteneurs. Utilisez des contrôles applicatifs : exécutez des requêtes DB, vérifiez des comptes, validez un client connu,
ou exécutez une transaction métier en lecture seule. Automatisez ces vérifications dans l’exercice.

8) Dois‑je restaurer Redis ou d’autres caches ?

Typiquement non — les caches sont reconstruisables et les restaurer peut réintroduire un état incorrect. Mais vous devez confirmer que l’application tolère un cache vide
et que la configuration du cache (mots de passe, TLS, politiques maxmemory) est sauvegardée.

9) Qu’en est‑il des secrets dans les variables d’environnement ?

Si votre production dépend d’un fichier env, ce fichier fait partie de l’état de déploiement et doit être récupérable. Mieux : migrez les secrets vers un gestionnaire de secrets
ou l’équivalent Docker secrets, et incluez la récupération break‑glass dans l’exercice.

10) Puis‑je faire cela avec Docker Compose et rester « enterprise‑grade » ?

Oui, si vous traitez Compose comme un artefact avec versioning, images figées, restaurations testées et gestion d’état disciplinée.
« Enterprise‑grade » est un comportement, pas un choix d’outil.

Conclusion : prochaines étapes réalisables cette semaine

Si vous ne faites qu’une chose, planifiez un exercice de restauration sur un hôte neuf et chronométrez‑le. Pas en production, pas sur votre portable, pas « un de ces jours ».
Mettez‑le au calendrier et invitez qui gère les sauvegardes, le stockage et l’application. Vous voulez tous les modes de défaillance dans la même salle.

Ensuite faites ces étapes, dans l’ordre :

  1. Inventoriez les montages pour chaque conteneur stateful et notez les chemins et noms de volumes faisant autorité.
  2. Séparez les artifacts en données (volumes), montages bind et configuration de déploiement pour restaurer de façon chirurgicale.
  3. Validez l’intégrité du jeu de sauvegarde le plus récent et prouvez que vous avez les clés pour le déchiffrer avec les permissions d’astreinte.
  4. Restaurez dans un sandbox et exécutez des vérifications applicatives, pas seulement « le conteneur tourne ».
  5. Mesurez le RTO, identifiez l’étape la plus lente, et corrigez cette chose avant d’optimiser autre chose.

Les sauvegardes que vous n’avez jamais restaurées ne sont pas des sauvegardes. Ce sont de l’optimisme compressé. Exécutez l’exercice, notez ce qui a cassé, et rendez‑le ennuyeux.

ZFS ECC vs non-ECC : Mathématiques du risque pour des déploiements réels

Si vous utilisez ZFS assez longtemps, vous vous poserez tôt ou tard la même question inconfortable : « Ai‑je vraiment besoin de RAM ECC, ou ce n’est que du folklore de gens qui aiment les cartes mères chères ? »
La réponse honnête est ennuyeuse et tranchante : cela dépend de votre budget de risque, de la valeur de vos données et de l’utilisation réelle de votre pool ZFS — pas des impressions, de la doctrine des forums ou d’une capture d’écran effrayante.

ZFS est excellent pour détecter la corruption. Il n’est pas magique pour empêcher la corruption qui survient avant que le checksum soit calculé, ni pour empêcher la corruption qui survient au mauvais endroit au mauvais moment.
Cet article présente les mathématiques, les modes de défaillance et le plan opérationnel — pour que vous puissiez prendre une décision défendable lors d’un post‑mortem.

Ce que change l’ECC (et ce que ça ne change pas)

La mémoire ECC (Error‑Correcting Code) n’est pas « plus rapide » et ce n’est pas un talisman. C’est un contrôle : elle détecte et corrige certaines classes d’erreurs mémoire (typiquement les erreurs d’un seul bit) et détecte (mais peut ne pas corriger) certaines erreurs multi‑bits.
Elle réduit la probabilité qu’une faute mémoire transitoire devienne des données pourries persistantes écrites sur disque.

Le non‑ECC n’est pas une « corruption garantie ». C’est simplement un risque non géré. La plupart des systèmes tourneront longtemps sans problème visible.
Puis, un jour, lors d’un scrub, d’un resilver, d’un fort churn d’ARC, de mises à jour de métadonnées ou d’une période de mémoire serrée, vous obtenez une erreur de checksum inexplicable — ou pire, vous n’en obtenez pas parce que la mauvaise chose a été checksummée.

Voici un cadrage pratique :

  • L’ECC réduit l’incertitude. Vous aurez toujours besoin de redondance, de scrubs, de sauvegardes, de monitoring et de restaurations testées.
  • L’ECC est le plus utile là où ZFS est le plus sollicité. Charges metadata‑intensives, dédup, fort churn d’ARC, special vdevs et gros pools qui scrubbent pendant des jours.
  • L’ECC ne corrige pas une mauvaise planification. Si votre seule copie est sur un pool unique, votre vrai problème est « pas de sauvegarde », pas « pas d’ECC ».

Une idée paraphrasée qui devrait accompagner chaque décision de stockage : l’espoir n’est pas une stratégie. — attribué à Vince Lombardi dans la culture ops, mais traitez‑la comme un proverbe.

Faits et contexte historique (ceux que vous pouvez utiliser)

  1. Les erreurs soft sont connues depuis longtemps. « Les rayons cosmiques inversent des bits » sonne comme de la science‑fiction, mais c’est mesuré dans des flottes de production depuis des décennies.
  2. La densité de la DRAM a rendu les erreurs plus pertinentes. À mesure que les cellules ont rétréci, la marge pour le bruit et la fuite de charge s’est réduite ; les taux d’erreur sont devenus visibles à grande échelle.
  3. L’ECC est devenu standard sur les serveurs parce que la disponibilité coûte cher. Pas parce que les serveurs seraient moralement supérieurs, mais parce que les pannes et interruptions ont un coût financier.
  4. ZFS a popularisé les checksums de bout en bout pour les admins mainstream. Le checksumming des données et métadonnées n’est pas unique à ZFS, mais ZFS l’a rendu opérationnellement accessible.
  5. Les scrubs sont un changement culturel. Les RAID traditionnels detectaient souvent la dégradation seulement lors d’une reconstruction ; ZFS normalise la vérification périodique de tout.
  6. Le copy‑on‑write change le rayon d’impact. ZFS n’écrase pas en place, ce qui réduit certains patterns de corruption mais en introduit d’autres (surtout autour des mises à jour de métadonnées).
  7. La dédup a été une leçon d’humilité. La dédup ZFS peut fonctionner, mais c’est une fonction vorace en mémoire qui transforme de petites erreurs en grosses pannes.
  8. Le « NAS grand public » a mûri. Les labs domestiques et PME ont commencé à exécuter des pools ZFS multi‑disques avec des attentes d’entreprise, souvent sur de la RAM et des cartes mères grand public.

Où les erreurs mémoire affectent ZFS : un modèle de défaillance

1) Le problème du timing du checksum

ZFS protège les blocs avec des checksums stockés séparément. Très bien. Mais il existe une fenêtre temporelle : le checksum est calculé sur des données en mémoire.
Si les données sont corrompues avant que le checksum soit calculé, ZFS calcule fidèlement un checksum des octets corrompus et écrit les deux. Ce n’est pas une « corruption silencieuse » à l’intérieur de ZFS ; c’est « des données fausses valablement checksummées ».

L’ECC aide en réduisant la probabilité que les octets alimentant le checksum soient erronés.
Le non‑ECC signifie que vous misez sur le fait que les erreurs transitoires n’apparaîtront pas dans cette fenêtre assez souvent pour poser problème.

2) Les métadonnées sont là où votre journée bascule

La corruption de données est pénible. La corruption de métadonnées est existentielle. Les métadonnées ZFS incluent les pointeurs de blocs, les spacemaps, les métadonnées d’allocation, les structures du MOS, les dnodes, et plus.
Un bit défectueux dans les métadonnées peut signifier :

  • un problème d’import du pool irrécupérable
  • un dataset qui ne monte pas
  • un objet qui pointe vers le mauvais bloc
  • un resilver qui se comporte « bizarrement » parce qu’il suit des pointeurs endommagés

ZFS est résilient, mais pas infaillible. Votre redondance (mirror/RAIDZ) aide si la corruption est sur disque et détectable.
Si les mauvaises métadonnées sont écrites, la redondance peut répliquer l’erreur parce qu’il s’agit d’un écrit logiquement consistant.

3) ARC, churn d’éviction et « la RAM comme multiplicateur de défaillance »

L’ARC est le cache en mémoire de ZFS. C’est une fonctionnalité de performance, mais aussi un endroit où un bit inversé peut être amplifié :
les mauvaises données mises en cache peuvent être servies, réécrites ou utilisées pour construire un état dérivé.

Sous pression mémoire, l’ARC évince agressivement. Ce churn augmente le nombre de transactions mémoire et la quantité de données touchées.
Plus de données touchées signifie plus d’opportunités pour qu’une faute ait de l’impact.

4) Special vdevs et accélération des petits blocs de métadonnées

Les special vdevs (souvent des miroirs SSD contenant métadonnées et petits blocs) sont une fusée de performance et une mine anti‑personnelle de fiabilité.
Si vous perdez ce vdev et n’avez pas de redondance, vous pouvez perdre le pool. Si vous corrompez ce qui y est écrit et que la corruption est valablement checksummée, vous pouvez perdre l’intégrité des structures les plus importantes.

5) Scrub, resilver et les phases de « forte lecture »

Les scrubs et les resilvers lisent beaucoup. Ils stressent aussi la chaîne : CPU, mémoire, HBA, câblage, disques.
Ce sont des moments où les problèmes latents se manifestent.
Si vous utilisez du non‑ECC, ces opérations sont votre tirage au sort, car elles poussent d’énormes volumes de données à travers la RAM.

Blague #1 : Si votre calendrier de scrub est « quand je m’en souviens », félicitations — vous avez inventé le bit rot de Schrödinger.

Mathématiques du risque appliquées aux déploiements réels

La plupart des arguments sur l’ECC s’embourbent sur des absolus : « Il faut l’avoir » contre « Je n’ai jamais eu de problème ».
Les décisions en production vivent de probabilités et de coûts. Modélisons donc de façon utilisable.

L’équation centrale : taux × exposition × conséquence

Vous n’avez pas besoin du taux exact de bit‑flip dû aux rayons cosmiques pour faire des calculs utiles. Il vous faut :

  • Taux d’erreur (R) : à quelle fréquence les erreurs mémoire surviennent (corrigées ou non). Cela varie énormément selon le matériel, l’âge, la température et la qualité des DIMM.
  • Exposition (E) : combien de données et de métadonnées transitent par la mémoire dans une fenêtre « dangereuse » (écritures, mises à jour de métadonnées, fenêtres de checksumming, pipeline scrub/resilver).
  • Conséquence (C) : quel est le coût lorsqu’un incident survient (d’un fichier isolé corrompu à « pool impossible à importer »).

Votre risque n’est pas « R ». Votre risque est R × E × C.

Le risque n’est pas réparti uniformément selon les charges

Une archive média majoritairement en lecture après ingestion a un profil d’exposition différent de :

  • un datastore de VM avec churn constant
  • une base de données avec latence serrée et écritures synchrones
  • une cible de sauvegarde qui reçoit de grands flux séquentiels et des purge fréquentes
  • un environnement intensif en dédup qui transforme les métadonnées en vos données les plus chaudes

Définissez votre « unité de perte »

Cessez de débattre en abstraction. Décidez ce que signifie une perte pour vous :

  • Unité A : un fichier corrompu restauré proprement depuis une sauvegarde (gênant)
  • Unité B : une VM avec corruption du système de fichiers (douloureux)
  • Unité C : échec d’import du pool, restauration sur plusieurs jours et une revue d’incident avec la direction (aux conséquences professionnelles)

L’ECC réduit principalement la probabilité des événements Unité B/C. Ce n’est pas une question pour votre collection de MP3 ; c’est une question de rayon d’impact.

Les sauvegardes modifient la conséquence, pas la probabilité

Des sauvegardes robustes réduisent C. L’ECC réduit R.
Si vous avez les deux, vous obtenez un bénéfice multiplicatif : moins d’incidents, et des incidents moins coûteux.

Pourquoi « ZFS checksums rendent l’ECC superflu » est une idée fausse répandue

Les checksums ZFS vous protègent quand :

  • le disque renvoie des données incorrectes
  • le câblage/HBA corrompt des bits en transit depuis le disque
  • la rotation secteur sur disque survient

Les checksums ZFS ne garantissent pas la protection quand :

  • des données erronées sont checksummées puis écrites
  • les pointeurs de métadonnées sont corrompus avant checksum
  • votre application écrit des données pourries et ZFS les préserve fidèlement

L’ECC est un contrôle amont qui réduit la probabilité que des « mauvaises données deviennent la vérité ».

Alors quelle est la recommandation réelle ?

Si votre pool contient données métier, des données irremplaçables ou des données dont la corruption est difficile à détecter au niveau applicatif, l’ECC est le choix par défaut approprié.
Le non‑ECC peut être défendable pour :

  • des caches jetables
  • des réplicas secondaires où l’intégrité du primaire est protégée
  • des labs domestiques où le temps d’indisponibilité est acceptable et où les sauvegardes sont réelles (testées)
  • du stockage froid où l’ingestion est contrôlée et vérifiée

Si votre plan est « je remarquerai la corruption », vous supposez que la corruption est bruyante. Souvent, elle ne l’est pas.

Quand le non‑ECC est acceptable (et quand c’est imprudent)

Acceptable : vous pouvez tolérer des données erronées et restaurer rapidement

Le non‑ECC peut convenir lorsque :

  • vos données sont répliquées ailleurs (et vous vérifiez les réplicas)
  • vous pouvez effacer et reconstruire le pool depuis la source de vérité
  • votre hôte ZFS n’effectue pas de travail lourd sur les métadonnées (pas de dédup, pas de special vdev)
  • vous scrubbiez régulièrement et surveillez les tendances d’erreurs

Imprudent : le pool est la source de vérité

Le non‑ECC est un mauvais pari quand :

  • vous avez un seul pool contenant la seule copie des données de production
  • vous utilisez ZFS pour le stockage de VM avec écritures constantes et snapshots
  • vous avez activé la dédup parce que quelqu’un a dit que ça « économise de l’espace »
  • vous fonctionnez près des limites mémoire et l’ARC est constamment sous pression
  • vous utilisez des special vdevs sans redondance, ou des SSD grand public sans protection en cas de perte de puissance

Dans ces scénarios, l’ECC coûte peu par rapport au premier incident où vous devez expliquer pourquoi les données sont « cohérentes mais fausses ».

Trois mini‑histoires d’entreprise issues du terrain

Mini‑histoire 1 : L’incident causé par une hypothèse erronée

Une entreprise de taille moyenne exploitait un cluster VM reposant sur ZFS pour des services internes. Les hôtes étaient des machines de bureau réaffectées : beaucoup de cœurs, beaucoup de RAM, pas d’ECC.
L’ingénieur stockage avait plaidé pour des cartes mères serveur, mais les achats avaient retenu « ZFS a des checksums » et traduit ça par « l’ECC est optionnel ».

Tout semblait correct jusqu’à une fenêtre de maintenance routinière : mise à jour du noyau, reboot, puis un scrub programmé a démarré automatiquement.
En plein scrub, un hôte a commencé à journaliser des erreurs de checksum. Pas beaucoup. Juste assez pour vous mettre mal à l’aise. Le pool est resté en ligne, le scrub s’est finalement terminé, et l’équipe a classé ça comme « un disque capricieux ».

Au cours des deux semaines suivantes, des problèmes d’application sporadiques sont apparus : la base SQLite d’un service a renvoyé des erreurs « malformed ». Le système de fichiers d’une autre VM a dû être réparé après un arrêt non propre.
L’équipe a chassé des faux‑sens : latence stockage, coupures réseau, SSD suspect.

Le point de bascule a été la comparaison des sauvegardes : restaurer la même image VM depuis deux snapshots différents produisait deux checksums différents pour quelques blocs.
Ce n’est pas de la « pourriture disque », c’est « quelque chose a écrit des vérités inconsistantes à différents moments ».

Après une analyse douloureuse, ils ont trouvé un pattern : les erreurs de checksum apparaissaient lors d’activités mémoire élevées. Les logs hôtes montraient des symptômes semblables à des MCE sur une machine, mais rien de définitif parce que la plateforme ne remontait pas bien la télémétrie mémoire.
Le remplacement des DIMM a réduit les erreurs, mais n’a pas restauré la confiance. Ils ont remplacé la plateforme par des systèmes compatibles ECC et ajouté des tests de restauration mensuels.

L’hypothèse erronée n’était pas « le non‑ECC corrompt toujours les données ». L’hypothèse erronée était « les checksums rendent l’exactitude en amont sans importance ».
Les checksums détectent les mensonges. Ils ne vous empêchent pas de les écrire.

Mini‑histoire 2 : L’optimisation qui s’est retournée contre eux

Une autre équipe utilisait ZFS pour un dépôt de sauvegarde. La pression d’espace était réelle, alors quelqu’un a proposé déduplication plus compression. Sur le papier, c’était brillant : les sauvegardes sont répétitives, la dédup devrait être efficace, et ZFS l’a intégré.
Ils ont activé la dédup sur un grand dataset et ont vu les gains augmenter. Tout le monde se sentait malin.

Puis sont arrivées les plaintes de performance. Les fenêtres d’ingestion ont glissé. La machine a commencé à swapper sous charge.
L’équipe a réagi en ajustant l’ARC et en ajoutant un SSD rapide pour L2ARC, essayant de « mettre en cache » le problème. Ils ont aussi augmenté le recordsize pour améliorer le débit.

Ce qu’ils n’avaient pas compris : la dédup pousse une énorme quantité de métadonnées en mémoire. La DDT (dedup table) est vorace. Sous pression mémoire, tout devient plus lent, et le système devient plus vulnérable aux cas limites.
Ils tournaient en non‑ECC parce que « ce ne sont que des sauvegardes », et parce que la plateforme était à l’origine une appliance optimisée pour le coût.

La panne n’a pas été immédiate, ce qui rend l’enseignement d’autant plus précieux. Après quelques mois, un scrub a trouvé des erreurs de checksum dans des blocs de métadonnées.
Les restaurations ont commencé à échouer pour un sous‑ensemble de jeux de sauvegarde — la pire des situations, car les sauvegardes existaient, mais n’étaient pas fiables.

Le rollback a pris des semaines : désactiver la dédup pour les nouvelles données, migrer les sauvegardes critiques vers un nouveau pool, et exécuter une vérification complète des restaurations sur les ensembles les plus importants.
L’optimisation n’était pas malveillante ; elle était mal adaptée au matériel et à la maturité opérationnelle.

Mini‑histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la situation

Un groupe financier exploitait ZFS sur une paire de serveurs de stockage avec RAM ECC, des special vdevs miroir, et un calendrier que personne ne remettait en question : scrub hebdomadaire, tests SMART étendus mensuels, exercices de restauration trimestriels.
L’ensemble était presque offensivement peu glamour. Pas de dédup. Pas de tunables exotiques. Juste des miroirs et de la discipline.

Un trimestre, lors d’un exercice de restauration, ils ont remarqué qu’une restauration était plus lente que prévu et que l’hôte récepteur avait journalisé une poignée d’erreurs mémoire corrigées.
Rien ne s’est crashé. Aucune donnée n’a été perdue. Mais la télémétrie existait, et l’exercice a forcé l’équipe à l’examiner alors que personne n’était en crise.

Ils ont remplacé le DIMM de manière proactive, puis exécuté un autre exercice de restauration et un scrub. Propre.
Deux semaines plus tard, le DIMM jumelé remplacé (même lot) a commencé à rapporter des erreurs corrigées sur un autre serveur. Ils l’ont aussi remplacé.

La partie amusante est ce qui ne s’est pas produit : aucun incident client, aucune corruption de pool, aucun « depuis combien de temps ça dure ? » en réunion.
L’ECC n’a pas « sauvé la journée » tout seul. La pratique ennuyeuse l’a fait : surveiller les erreurs corrigées, les traiter comme un signal de dégradation matérielle et valider les restaurations tant que c’était un événement calendaire plutôt qu’une crise.

Guide de diagnostic rapide : trouver rapidement le goulot

Quand ZFS commence à mal se comporter — erreurs de checksum, scrubs lents, blocages aléatoires — vous pouvez perdre des jours à débattre de l’ECC comme si c’était de la théologie.
Ce playbook est pour le moment où vous avez besoin de réponses rapides.

Premier point : confirmez quel type de défaillance vous avez

  • Défaillance d’intégrité : erreurs de checksum, fichiers corrompus, erreurs de pool en augmentation.
  • Défaillance disponibilité/performance : blocages I/O, scrub qui prend une éternité, latences élevées, timeouts.
  • Pression sur les ressources : swapping, OOM kills, thrash ARC, saturation CPU.

Deuxième point : isolez « chemin disque » vs « chemin mémoire/CPU »

  • Si zpool status montre des erreurs de checksum sur un périphérique spécifique, suspectez d’abord le disque/câble/port HBA ou le backplane.
  • Si des erreurs apparaissent sur plusieurs périphériques simultanément, suspectez HBA, backplane, RAM ou CPU.
  • Si le pool est propre mais que les applis voient de la corruption, suspectez des bugs applicatifs, la RAM ou la couche réseau au‑dessus du stockage.

Troisième point : décidez si vous pouvez garder le système en ligne

  • Les erreurs mémoire corrigées sont un avertissement. Vous pouvez généralement rester en ligne, mais planifiez une fenêtre de maintenance.
  • Erreurs non corrigibles ou augmentation des checksum : stoppez les écritures, snapshottez ce que vous pouvez et planifiez un basculement/restauration contrôlé.
  • Resilver/scrub sur du matériel instable : risqué. Réparez la plateforme d’abord si possible.

Tâches pratiques : commandes, sorties et décisions

Ce sont des tâches réelles que vous pouvez exécuter sur Linux avec OpenZFS. Chacune indique ce qu’il faut rechercher et la décision à prendre.
(Si vous êtes sur FreeBSD, les commandes diffèrent, mais la logique opérationnelle reste la même.)

Task 1: Check pool health and error counters

cr0x@server:~$ zpool status -v tank
  pool: tank
 state: ONLINE
status: One or more devices has experienced an unrecoverable error.
action: Determine if the device needs to be replaced, and clear the errors
  scan: scrub repaired 0B in 05:12:44 with 3 errors on Sun Dec  8 03:20:55 2025
config:

        NAME                        STATE     READ WRITE CKSUM
        tank                        ONLINE       0     0     0
          raidz1-0                  ONLINE       0     0     0
            ata-WDC_WD80...-part1   ONLINE       0     0     3
            ata-WDC_WD80...-part1   ONLINE       0     0     0
            ata-WDC_WD80...-part1   ONLINE       0     0     0
            ata-WDC_WD80...-part1   ONLINE       0     0     0

errors: Permanent errors have been detected in the following files:

        /tank/vmstore/vm-112-disk-0.qcow2

Ce que cela signifie : des erreurs de CKSUM sur un seul disque indiquent souvent un problème de disque, de câble, de port HBA ou de backplane. « Permanent errors » signifie que ZFS n’a pas pu reconstruire certains blocs.

Décision : Si la redondance ne peut pas réparer, restaurez le fichier impacté depuis une sauvegarde/snapshot. Ensuite, investiguez le chemin du périphérique (SMART, câblage). Ne faites pas de « clear and forget ».

Task 2: Show detailed pool properties that affect integrity and recovery

cr0x@server:~$ zpool get ashift,autotrim,autoexpand,autoreplace,listsnapshots tank
NAME  PROPERTY       VALUE   SOURCE
tank  ashift         12      local
tank  autotrim       off     default
tank  autoexpand     off     default
tank  autoreplace    off     default
tank  listsnapshots  off     default

Ce que cela signifie : ashift affecte l’amplification d’écriture et la performance. Ça ne résoudra pas les problèmes ECC, mais un mauvais ashift peut rendre les scrubs/resilvers extrêmement longs.

Décision : Si ashift est inadapté à vos disques, planifiez une migration (pas un changement rapide). Si les scrubs prennent des jours, votre fenêtre d’exposition augmente — une autre raison pour laquelle l’ECC devient plus précieuse.

Task 3: Confirm scrub schedule and last scrub outcome

cr0x@server:~$ zpool status tank | sed -n '1,20p'
  pool: tank
 state: ONLINE
  scan: scrub repaired 0B in 05:12:44 with 3 errors on Sun Dec  8 03:20:55 2025
config:

        NAME        STATE     READ WRITE CKSUM
        tank        ONLINE       0     0     0
          raidz1-0  ONLINE       0     0     0

Ce que cela signifie : Vous avez un scrub récent et il a trouvé des erreurs. Le scrub est votre système d’alerte précoce ; traitez‑le comme tel.

Décision : Si les scrubs trouvent régulièrement de nouvelles erreurs de checksum, cessez de supposer que c’est « aléatoire ». Suivez la tendance et escaladez vers une expertise matérielle.

Task 4: Check ZFS error logs and kernel messages around I/O

cr0x@server:~$ dmesg -T | egrep -i 'zfs|checksum|ata|sas|mce|edac' | tail -n 20
[Sun Dec  8 03:21:12 2025] ZFS: vdev I/O error, zpool=tank, vdev=/dev/sdb1, error=52
[Sun Dec  8 03:21:12 2025] ata3.00: status: { DRDY ERR }
[Sun Dec  8 03:21:12 2025] ata3.00: error: { UNC }
[Sun Dec  8 03:21:13 2025] mce: [Hardware Error]: CPU 0: Machine Check: 0 Bank 8: b200000000070005

Ce que cela signifie : Des erreurs I/O de stockage mélangées à des entrées MCE sont un signal d’alarme. Ne supposez pas que le disque est coupable si le CPU rapporte des machine checks.

Décision : Si MCE/EDAC suggère des problèmes mémoire, priorisez la stabilité RAM/plateforme avant de relancer un scrub/resilver qui pourrait écrire une nouvelle « vérité ».

Task 5: Verify ECC is actually enabled and recognized

cr0x@server:~$ sudo dmidecode -t memory | egrep -i 'error correction|ecc|type:|manufacturer' | head -n 20
        Error Correction Type: Multi-bit ECC
        Type: DDR4
        Manufacturer: Micron Technology
        Error Correction Type: Multi-bit ECC
        Type: DDR4
        Manufacturer: Micron Technology

Ce que cela signifie : La plateforme rapporte la capacité ECC. Cela ne garantit pas que Linux reçoive les événements EDAC, mais c’est une condition nécessaire.

Décision : Si cela rapporte « None » ou « Unknown », ne vous dites pas que vous avez l’ECC « parce que les DIMM sont ECC ». Il vous faut aussi une carte mère/CPU qui l’utilise.

Task 6: Check EDAC counters for corrected/uncorrected memory errors

cr0x@server:~$ sudo edac-util -v
edac-util: EDAC drivers are loaded. 1 MC detected:
  mc0: 2 Uncorrected Errors with no DIMM info
  mc0: 41 Corrected Errors with no DIMM info

Ce que cela signifie : Les erreurs corrigées signifient que l’ECC fait son travail. Les erreurs non corrigées signifient que le système a subi des fautes mémoire que l’ECC n’a pas pu réparer.

Décision : Toute erreur non corrigée : planifiez une maintenance immédiate et remplacez les DIMM/slots/carte mère suspects. Des erreurs corrigées en tendance : considérez‑les comme un signal de pré‑défaillance et planifiez un remplacement.

Task 7: Inspect per-DIMM error detail (when available)

cr0x@server:~$ sudo ras-mc-ctl --summary
Summary of memory errors:
Location: mc0/csrow0/channel0/dimm0
  Corrected: 37
  Uncorrected: 0
Location: mc0/csrow0/channel1/dimm0
  Corrected: 4
  Uncorrected: 0

Ce que cela signifie : Les erreurs sont localisées sur un DIMM/canal, ce qui est exactement ce que vous voulez pour un remplacement actionnable.

Décision : Remplacez le DIMM avec le plus grand nombre d’erreurs corrigées en premier, puis revérifiez. Si les erreurs persistent sur le même canal, suspectez le slot ou le contrôleur mémoire.

Task 8: Confirm ZFS is not doing dedup accidentally

cr0x@server:~$ zfs get -r dedup tank
NAME                PROPERTY  VALUE  SOURCE
tank                dedup     off    default
tank/vmstore        dedup     off    default
tank/backups        dedup     off    default

Ce que cela signifie : La dédup est désactivée, bien. Si elle est activée quelque part, la pression mémoire et la sensibilité des métadonnées augmentent fortement.

Décision : Si vous trouvez la dédup activée sans justification solide et plan de dimensionnement, désactivez‑la pour les nouvelles écritures (set dedup=off) et planifiez la migration des anciennes données si nécessaire.

Task 9: Check ARC size and memory pressure signals

cr0x@server:~$ arc_summary | egrep -i 'arc size|target size|memory|evict' | head -n 12
ARC size (current):                                   27.4 GiB
Target size (adaptive):                               30.1 GiB
Min size (hard limit):                                8.0 GiB
Max size (high water):                                32.0 GiB
Evict skips:                                          0
Demand data hits:                                     89.3%

Ce que cela signifie : L’ARC est important et stable. Si vous voyez des évictions constantes, de faibles taux de hit, ou que la machine swappe, vous êtes en état de fort churn où les fautes ont plus d’impact.

Décision : Si l’ARC thrash ou le swap est présent, réduisez la charge, ajoutez de la RAM, ou limitez l’ARC. Ne lancez pas de resilvers sur un hôte qui swap de façon étrange.

Task 10: Check for swapping and reclaim pressure

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:            64Gi        58Gi       1.2Gi       1.0Gi       4.8Gi       2.6Gi
Swap:           16Gi        12Gi       4.0Gi

Ce que cela signifie : L’utilisation active du swap sur un hôte de stockage est un signe de mauvaise performance et, indirectement, un amplificateur de risque d’intégrité (plus de churn, plus de stress lors d’opérations critiques).

Décision : Trouvez ce qui consomme la mémoire (VMs, dédup, workloads metadata‑intensifs). Ajoutez de la RAM ou réduisez l’empreinte. Si vous ne pouvez pas ajouter l’ECC, évitez au moins de fonctionner à haute charge et de swapper.

Task 11: Verify SMART health and UDMA CRC errors (cabling tells)

cr0x@server:~$ sudo smartctl -a /dev/sdb | egrep -i 'reallocated|pending|offline_uncorrectable|udma_crc_error_count' 
197 Current_Pending_Sector  0x0012   100   100   000    Old_age   Always       -       0
198 Offline_Uncorrectable   0x0010   100   100   000    Old_age   Offline      -       0
199 UDMA_CRC_Error_Count    0x003e   200   199   000    Old_age   Always       -       12

Ce que cela signifie : Les erreurs UDMA CRC impliquent généralement des câbles/backplanes plutôt que le média. Les erreurs de checksum ZFS qui corrèlent avec des incréments CRC sont souvent des « données qui ont été malmenées en transit ».

Décision : Remplacez les câbles, reseatez les connexions, vérifiez le port backplane/HBA. Ensuite, relancez un scrub pour confirmer la stabilité.

Task 12: Identify whether checksum errors are new or historical

cr0x@server:~$ zpool status -v tank | tail -n 15
errors: Permanent errors have been detected in the following files:

        /tank/vmstore/vm-112-disk-0.qcow2

Ce que cela signifie : Les « Permanent errors » persistent jusqu’à ce que vous restauriez/écrasiez les blocs affectés. Effacer les erreurs ne répare pas les données.

Décision : Restaurez le fichier à partir d’un snapshot/sauvegarde fiable ou supprimez‑le et régénérez‑le. Ensuite, exécutez zpool clear seulement après remédiation.

Task 13: Map a block-level problem to snapshots and attempt self-heal

cr0x@server:~$ zfs list -t snapshot -o name,creation -S creation tank/vmstore | head
NAME                                CREATION
tank/vmstore@hourly-2025-12-08-0300  Sun Dec  8 03:00 2025
tank/vmstore@hourly-2025-12-08-0200  Sun Dec  8 02:00 2025
tank/vmstore@daily-2025-12-07        Sat Dec  7 23:55 2025

Ce que cela signifie : Vous avez des snapshots pour revenir en arrière ou cloner, ce qui est votre chemin le plus rapide vers la correction.

Décision : Si un fichier est marqué comme corrompu de façon permanente, restaurez‑le depuis le snapshot connu bon le plus récent et validez au niveau applicatif.

Task 14: Force a targeted read to surface latent errors

cr0x@server:~$ sudo dd if=/tank/vmstore/vm-112-disk-0.qcow2 of=/dev/null bs=16M status=progress
2147483648 bytes (2.1 GB, 2.0 GiB) copied, 7 s, 307 MB/s
4294967296 bytes (4.3 GB, 4.0 GiB) copied, 14 s, 305 MB/s
...output...

Ce que cela signifie : Une lecture séquentielle complète peut déclencher la vérification des checksums et montrer si les erreurs réapparaissent. Ce n’est pas un substitut au scrub, mais c’est un outil de triage rapide pour un objet spécifique.

Décision : Si les lectures déclenchent de nouveaux erreurs de checksum, considérez le chemin sous‑jacent comme instable ; n’attendez pas le scrub hebdomadaire pour vous dire ce que vous savez déjà.

Task 15: Check scrub/resilver throughput and identify if you’re CPU-bound or I/O-bound

cr0x@server:~$ iostat -x 2 3
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          12.31    0.00    6.22   21.10    0.00   60.37

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s   w_await aqu-sz  %util
sdb              84.0  10432.0     0.0    0.0   28.4   124.2        3.0     64.0    2.1    2.40   98.0
sdc              82.0  10240.0     0.0    0.0   29.1   124.9        2.0     48.0    1.9    2.35   97.5

Ce que cela signifie : Un %iowait élevé et une utilisation disque proche de 100 % suggèrent que le scrub est limité par les disques. Si le CPU était saturé et les disques inoccupés, vous seriez limité par le CPU/checksum.

Décision : Limité par les disques : vérifiez la topologie vdev, ashift, la santé des disques et le câblage. Limité par le CPU : envisagez un CPU plus rapide, activez les offloads de checksum si disponible, ou réduisez le recordsize/le churn des métadonnées.

Task 16: Confirm special vdev redundancy (if you use one)

cr0x@server:~$ zpool status tank | sed -n '1,80p'
  pool: tank
 state: ONLINE
config:

        NAME                       STATE     READ WRITE CKSUM
        tank                       ONLINE       0     0     0
          raidz2-0                 ONLINE       0     0     0
            sda1                   ONLINE       0     0     0
            sdb1                   ONLINE       0     0     0
            sdc1                   ONLINE       0     0     0
            sdd1                   ONLINE       0     0     0
          special                  ONLINE       0     0     0
            mirror-1               ONLINE       0     0     0
              nvme0n1p1            ONLINE       0     0     0
              nvme1n1p1            ONLINE       0     0     0

Ce que cela signifie : Le special vdev est en miroir. C’est la ligne de sécurité minimale si vous y placez des métadonnées.

Décision : Si le special est un seul périphérique, corrigez cela avant d’optimiser autre chose. Un special vdev unique est un point de défaillance unique du pool.

Blague #2 : Exécuter ZFS avec la dédup activée sur du non‑ECC, c’est comme jongler avec des tronçonneuses parce que ça « économise des étapes ».

Erreurs courantes : symptôme → cause racine → correction

1) « Erreurs de checksum “aléatoires” sur plusieurs disques »

  • Symptôme : l’incrément de CKSUM sur plus d’un disque, parfois différents disques certains jours.
  • Cause racine : problème de chemin partagé (HBA, backplane, alimentation, câblage) ou instabilité mémoire/CPU provoquant l’écriture/validation de mauvaises données.
  • Correction : Vérifiez les CRC SMART, changez câbles/ports, mettez à jour le firmware HBA, consultez les logs MCE/EDAC, exécutez memtest en maintenance et stoppez les écritures jusqu’à stabilisation.

2) « ZFS dit réparé, mais l’application est toujours cassée »

  • Symptôme : le scrub indique des réparations, mais la base de données/le format de fichier se plaint toujours.
  • Cause racine : ZFS a réparé des blocs corrompus via la redondance, mais l’état applicatif avait déjà intégré de mauvais écrits (surtout si la corruption est survenue avant checksum).
  • Correction : Restaurez depuis des sauvegardes/snapshots applicativement cohérents. Ajoutez des checksums côté application quand c’est possible (les bases de données en possèdent souvent).

3) Les scrubs sont propres, mais vous ne faites toujours pas confiance au pool

  • Symptôme : pas d’erreurs ZFS, mais vous avez eu des crashs inexplicables, des panics noyau ou des rapports de corruption de fichiers.
  • Cause racine : instabilité mémoire qui affecte le calcul et le comportement applicatif plus que les lectures disque, ou corruption survenant avant que les données n’atteignent ZFS.
  • Correction : Vérifiez EDAC/MCE, exécutez des tests mémoire, validez l’alimentation et la thermique, validez avec des checksums applicatifs de bout en bout, et envisagez l’ECC si le pool est la source de vérité.

4) « Nous avons effacé les erreurs et tout va bien »

  • Symptôme : quelqu’un a exécuté zpool clear et a déclaré victoire.
  • Cause racine : confusion entre compteurs et corruption. L’effacement réinitialise le reporting, pas la réalité.
  • Correction : Identifiez et remédiez aux fichiers endommagés (restauration/écrasement). N’effacez qu’après avoir réparé les données et stabilisé le matériel.

5) Le pool ne s’importe pas après un événement de coupure

  • Symptôme : l’import échoue ou se bloque après une coupure d’alimentation abrupte.
  • Cause racine : problèmes matériel/firmware, mauvaise mémoire ou chemin de stockage instable exposé par la lourde relecture et les opérations metadata au démarrage.
  • Correction : Validez la RAM (logs ECC ou memtest), vérifiez le firmware HBA, assurez‑vous d’un traitement correct des pertes de puissance (UPS), et documentez/testez les environnements de démarrage et de récupération.

6) « Nous avons ajouté de la RAM et maintenant nous avons des erreurs »

  • Symptôme : des erreurs apparaissent après une mise à niveau mémoire.
  • Cause racine : types/timings DIMM mélangés, DIMM marginal, mauvais réglages BIOS ou carte incapable de piloter la configuration de façon fiable.
  • Correction : Utilisez des configurations mémoire validées, mettez à jour le BIOS, réduisez la vitesse aux réglages stables et surveillez les compteurs EDAC. Remplacez tôt les DIMM suspects.

Listes de contrôle / plan étape par étape

Checklist de décision : ce système ZFS doit‑il utiliser l’ECC ?

  1. Ce pool est‑il une source de vérité ? Si oui, préférez l’ECC par défaut.
  2. La corruption est‑elle difficile à détecter ? Images de VM, bases de données, photos, données scientifiques : oui. Préférez l’ECC.
  3. Utilisez‑vous dédup, special vdevs ou snapshots intensifs ? Si oui, l’ECC est fortement recommandé.
  4. Pouvez‑vous restaurer rapidement et l’avez‑vous testé ? Si non, l’ECC ne vous sauvera pas, mais le non‑ECC vous nuira davantage.
  5. Avez‑vous de la télémétrie pour les erreurs mémoire ? Si non, vous volez à l’aveugle — préférez des plateformes ECC avec visibilité EDAC.

Checklist opérationnelle : si vous devez absolument tourner sans ECC

  1. Gardez‑le simple : mirrors/RAIDZ, pas de dédup, évitez les special vdevs mono‑périphérique.
  2. Exécutez des scrubs réguliers et alertez immédiatement sur toute nouvelle erreur de checksum.
  3. Conservez une marge mémoire : évitez le swap ; limitez l’ARC si nécessaire.
  4. Utilisez des checksums applicatifs quand c’est possible (vérifications bases de données, hachages pour archives).
  5. Ayez des sauvegardes vérifiées : tests de restauration périodiques, pas « on a des sauvegardes quelque part ».
  6. Maintenez un plan de pièces de rechange : câbles connus bons, HBA de rechange, disque de rechange, et procédure de remplacement documentée.

Étape par étape : répondre aux premières erreurs de checksum

  1. Gelez les hypothèses : ne déclarez pas « disque défaillant » tout de suite.
  2. Capturez la sortie de zpool status -v et les logs système autour de l’événement.
  3. Vérifiez SMART, en particulier les compteurs CRC et les secteurs en attente.
  4. Vérifiez les compteurs MCE/EDAC. Si des erreurs corrigées existent, considérez le matériel en dégradation.
  5. Identifiez les fichiers affectés ; restaurez depuis snapshot/sauvegarde si possible.
  6. Corrigez la couche physique (câble/port/HBA) avant de relancer un scrub.
  7. Exécutez un scrub et vérifiez que la tendance des erreurs est plate.
  8. Si les erreurs réapparaissent sur plusieurs périphériques, planifiez une maintenance pour isoler RAM/HBA/backplane.

FAQ

1) ZFS nécessite‑t‑il de la RAM ECC ?

ZFS ne nécessite pas d’ECC pour fonctionner. L’ECC est un contrôle de fiabilité. Si le pool contient des données importantes, l’ECC est le choix par défaut approprié.

2) Si ZFS a des checksums, comment la corruption mémoire peut‑elle encore avoir de l’importance ?

Les checksums détectent la corruption après que le checksum a été calculé. Si des données corrompues sont checksummées et écrites, ZFS les validera ensuite comme « correctes », parce qu’elles correspondent à leur checksum.

3) Le non‑ECC est‑il acceptable pour un NAS domestique ?

Parfois. Si vous avez de vraies sauvegardes et que vous pouvez tolérer des restaurations occasionnelles, le non‑ECC peut être un compromis acceptable.
Si vous stockez des photos irremplaçables et que votre « sauvegarde » est un autre disque dans la même machine, vous jouez, vous n’ingéniez pas.

4) Qu’est‑ce qui est pire : pas de scrub ou pas d’ECC ?

L’absence de calendrier de scrub est généralement pire à court terme car vous découvrirez les problèmes disques latents seulement lors d’une reconstruction — quand vous pouvez le moins vous permettre des surprises.
L’absence d’ECC augmente la probabilité que certaines surprises soient plus étranges et plus difficiles à attribuer.

5) Les mirrors/RAIDZ rendent‑ils l’ECC moins important ?

La redondance aide lorsque la corruption est sur disque et detectable. L’ECC aide à prévenir les mauvais écrits et protège les opérations en mémoire.
Ils traitent des modes de défaillance différents ; ils sont complémentaires, pas substitutifs.

6) Puis‑je « valider » mon système non‑ECC en exécutant memtest une fois ?

Memtest est utile, mais c’est un test ponctuel. Certaines défaillances dépendent de la température ou de la charge et n’apparaissent qu’après des mois.
Si vous prenez l’intégrité au sérieux, préférez l’ECC plus la surveillance pour voir les erreurs corrigées avant qu’elles ne deviennent des incidents.

7) Quelles fonctionnalités ZFS rendent l’ECC plus importante ?

La dédup, les special vdevs, le snapshotting/clonage intensif, les charges metadata‑intensives et les systèmes tournant près des limites mémoire.
Elles augmentent la quantité d’état critique touché en mémoire et le coût d’une erreur.

8) Si je vois des erreurs ECC corrigées, dois‑je paniquer ?

Non. Les erreurs corrigées signifient que l’ECC a fait son travail. Mais ne les ignorez pas. Une tendance à la hausse est un signal de maintenance : remplacez le DIMM, vérifiez le refroidissement et validez les réglages BIOS.

9) L’ECC suffit‑il à garantir l’intégrité ?

Non. Vous avez toujours besoin de redondance, de scrubs, de sauvegardes et de validations. L’ECC réduit une classe de risque amont ; elle ne rend pas votre système invincible ni vos sauvegardes optionnelles.

10) Quelle est la mise à niveau de fiabilité la moins chère si je ne peux pas obtenir d’ECC ?

La discipline opérationnelle : scrubs, surveillance SMART, tests de restauration et empêcher le système de swapper. Simplifiez aussi le pool (mirrors) et évitez les fonctionnalités risquées (dédup, special vdev mono‑périphérique).

Prochaines étapes actionnables cette semaine

  1. Décidez votre unité de perte. Si la perte du pool a des conséquences professionnelles, achetez du matériel compatible ECC ou déplacez la charge.
  2. Activez et surveillez les signaux pertinents. Suivez la santé via zpool status, les résultats de scrub, les CRC/pending sectors SMART et les compteurs EDAC/MCE.
  3. Planifiez des scrubs et testez les restaurations. Les scrubs détectent les problèmes ; les tests de restauration prouvent que vous pouvez y survivre.
  4. Auditez vos fonctionnalités ZFS. Si la dédup est activée « pour économiser de l’espace », désactivez‑la pour les nouvelles écritures et repensez la conception avant de la réintroduire.
  5. Si vous restez en non‑ECC, réduisez l’exposition. Gardez de la marge mémoire, évitez le swap et conservez une topologie de pool conservatrice.

La position mûre n’est ni « toujours ECC » ni « jamais ECC ». C’est : connaissez vos modes de défaillance, évaluez vos conséquences et choisissez le matériel qui correspond au sérieux de vos engagements.
ZFS vous dira quand il détecte des mensonges. L’ECC aide à faire en sorte que vous ne les écriviez pas en premier lieu.

Empilement 3D et l’avenir des chiplets : vers où vont les CPU

À 02:17, votre téléphone de garde vibre. La latence augmente, le CPU est « seulement » à 55 % et quelqu’un dans le canal de discussion dit : « Ça doit être le réseau. » Vous regardez les graphiques et ressentez cette angoisse familière : le système est lent, mais d’aucune façon votre ancien modèle mental ne peut l’expliquer.

Bienvenue dans l’ère où les processeurs ne sont plus une dalle monolithique de silicium. Ce sont des quartiers de chiplets, reliés par des liaisons haute vitesse, parfois avec du silicium empilé par-dessus comme un gratte‑ciel. Les modes de défaillance sont différents. Les leviers de réglage sont différents. Et si vous continuez à traiter un package moderne comme un CPU uniforme unique, vous continuerez d’envoyer des mystères en production.

Pourquoi les CPU ont changé : physique, économie et la fin du « il suffit de réduire »

Pendant des décennies, on pouvait traiter le progrès des CPU comme un abonnement prévisible : chaque génération devenait plus dense, plus rapide et (globalement) moins chère par unité de calcul. Cette ère ne s’est pas terminée par un communiqué spectaculaire. Elle s’est terminée par mille compromis mineurs — courant de fuite, coût de la lithographie, variabilité et la vérité inconfortable que les fils ne suivent pas la même courbe d’échelle que les transistors.

Quand vous entendez « chiplets » et « empilement 3D », ne le traduisez pas par « ingénierie astucieuse ». Traduisez‑le plutôt par : les vieilles hypothèses économiques et physiques ont cassé, donc le packaging est devenu la nouvelle architecture. L’innovation migre de l’intérieur d’une puce vers entre puces.

Faits et contexte historique (le genre qui vous aide vraiment à raisonner)

  • Fait 1 : Le scaling de Dennard (densité de puissance restant stable avec la réduction des transistors) s’est effectivement arrêté au milieu des années 2000, forçant le ralentissement de la montée en fréquence et poussant vers des designs multicœurs.
  • Fait 2 : La latence des interconnexions est un goulot notable depuis des années ; les fils sur puce ne deviennent pas proportionnellement plus rapides à chaque nœud, donc « plus grand die » signifie plus de temps passé à déplacer des bits.
  • Fait 3 : Les limites de réticule plafonnent la taille d’une exposition lithographique ; les très grands dies deviennent des cauchemars de rendement sauf si on les segmente ou les assemble.
  • Fait 4 : L’industrie utilise des modules multi‑puces depuis longtemps (pensez : premiers packages double‑die, modules serveurs), mais les chiplets d’aujourd’hui sont bien plus standardisés et critiques pour la performance.
  • Fait 5 : La mémoire à large bande passante (HBM) est devenue pratique en empilant des dies DRAM et en les reliant par TSVs, démontrant que l’intégration verticale peut surpasser la bande passante DIMM traditionnelle.
  • Fait 6 : L’empilement de cache 3D dans des CPU courants a montré une leçon précise : ajouter de la SRAM verticalement peut booster la performance sans agrandir le die logique le plus chaud.
  • Fait 7 : Les cœurs hétérogènes (concept big/little) existent depuis des années dans le mobile ; ils sont désormais courants dans les serveurs parce que la puissance et les thermiques — pas la fréquence de pointe — définissent le débit.
  • Fait 8 : Le packaging avancé (interposers 2.5D, ponts silicium, fan‑out) est maintenant un différenciateur compétitif, pas un détail de fabrication en backend.

Résultat opérationnel : les 10–15 % de gains de performance suivants sont moins susceptibles de venir d’un nouvel ensemble d’instructions et plus susceptibles de venir d’une meilleure localité, de hiérarchies mémoire plus intelligentes et de liens die‑à‑die plus serrés. Si votre charge est sensible à la variance de latence, vous devez traiter le packaging et la topologie comme vous traitez le routage réseau.

Chiplets, interconnexions et pourquoi « socket » ne veut plus dire ce que vous pensez

Un CPU chiplet est un package contenant plusieurs dies, chacun spécialisé : cœurs, cache, contrôleurs mémoire, IO, accélérateurs, parfois même processeurs de sécurité. Le package est le produit. Le « CPU » n’est plus une dalle unique ; c’est un petit système distribué sous un heat spreader.

Les chiplets existent pour trois raisons franches :

  1. Rendement : les dies plus petits ont un meilleur rendement ; les défauts ne tuent pas un énorme die entier.
  2. Mélange de nœuds de process : logique rapide sur un nœud avancé, IO sur un nœud plus mature et moins cher.
  3. Agilité produit : réutiliser un IO die validé sur plusieurs SKUs ; varier le nombre de cœurs et les tuiles de cache sans tout refabriquer.

L’interconnect est maintenant de l’architecture

Dans un die monolithique, les chemins cœur→cache et cœur→mémoire sont majoritairement « internes ». Dans des chiplets, ces chemins peuvent traverser un fabric entre dies. L’interconnect a des caractéristiques de bande passante, latence et congestion, et il peut introduire des effets de topologie qui ressemblent étrangement à un problème réseau — sauf que vous ne pouvez pas y appliquer un tcpdump.

Les packages modernes utilisent des fabrics propriétaires, et l’industrie pousse vers des standards interopérables die‑à‑die comme UCIe. Le point clé n’est pas l’acronyme. C’est que les liaisons die‑à‑die sont traitées comme des IO haute vitesse : sérialisées, cadencées, gérées en puissance, entraînées, parfois relancées. Cela signifie que l’état des liens, les compteurs d’erreurs et les états d’alimentation peuvent affecter la performance de façons qui semblent « aléatoires » à moins de les mesurer.

Blague #1 : Les chiplets sont comme des microservices : tout le monde adore la flexibilité jusqu’à ce qu’il faille déboguer la latence à travers des frontières qu’on a créées volontairement.

NUMA n’était pas nouveau. Vous avez simplement arrêté de le respecter.

Les CPU chiplet transforment chaque serveur en une machine NUMA plus nuancée. Parfois les « nœuds NUMA » correspondent aux contrôleurs mémoire ; parfois ils correspondent aux complexes de cœurs ; parfois les deux. Dans tous les cas, la localité compte : quel cœur accède à quelle mémoire, quelle tranche de cache de dernier niveau est plus proche, et à quelle fréquence vous traversez l’interconnect.

Si votre playbook de performance commence et se termine par « ajoutez des cœurs » et « épinglez les threads », vous allez heurter le nouveau mur : la contention d’interconnect et de la hiérarchie mémoire. Le package CPU a maintenant des patterns de trafic internes, et votre charge peut créer des points chauds.

Empilement 3D : bande passante verticale, problèmes verticaux

L’empilement 3D consiste à utiliser plusieurs dies empilés verticalement avec des connexions denses (souvent TSVs, micro‑bumps ou hybrid bonding). On l’utilise pour le cache, la DRAM (HBM) et de plus en plus pour des arrangements logique‑sur‑logique.

Pourquoi empiler ?

  • Bande passante : les connexions verticales peuvent être bien plus denses que le routage bord à bord du package.
  • Latence : la distance physique plus courte peut réduire le temps d’accès pour certaines structures (surtout le cache).
  • Efficacité d’aire : vous pouvez ajouter de la capacité sans agrandir l’empreinte 2D d’un die logique chaud.

Mais on n’a rien sans contrepartie. L’empilement 3D introduit un triangle opérationnel désagréable : thermique, rendement et fiabilité.

Cache empilé : pourquoi ça marche

De la SRAM empilée sur un die de calcul vous donne un grand cache de dernier niveau sans rendre le die logique énorme. C’est un énorme gain pour des charges avec des jeux de travail juste au‑dessus des tailles de cache traditionnelles : beaucoup de jeux, certains flux EDA, certaines bases de données en mémoire, magasins clé‑valeur avec clés chaudes, et pipelines d’analytique avec scans répétés.

Du point de vue exploitation, le cache empilé change deux choses :

  1. La performance devient plus bimodale. Si votre charge tient dans le cache, vous êtes un héros. Si ce n’est pas le cas, vous retournez à la DRAM et l’avantage s’évapore.
  2. La marge thermique devient précieuse. Du silicium supplémentaire au‑dessus du die de calcul affecte l’écoulement de la chaleur ; le comportement turbo et les fréquences soutenues peuvent changer et se traduire par de la variance de latence.

HBM : l’astuce bande passante avec un prix

HBM empile des dies DRAM et les place près du die de calcul (souvent via un interposer). Cela fournit une énorme bande passante comparé aux DIMMs traditionnels, mais la capacité par stack est limitée et le coût est élevé. Cela change aussi la nature des défaillances et de l’observabilité : les erreurs mémoire peuvent apparaître différemment, et la planification de capacité devient un sport différent.

Le packaging 3D et 2.5D impose aussi une nouvelle règle de conception : votre logiciel doit comprendre les niveaux. HBM vs DDR, mémoire proche vs mémoire lointaine, cache sur package vs cache sur die. « Il suffit d’allouer la mémoire » devient une décision de performance.

Blague #2 : Empiler des dies, c’est super jusqu’à ce qu’on se souvienne que la chaleur s’empile aussi, et contrairement à votre backlog elle ne peut pas être reportée.

Le vrai ennemi : les octets, pas les FLOPS

La plupart des systèmes en production ne sont pas limités par le débit arithmétique brut. Ils sont limités par le déplacement des données : de la mémoire au cache, du cache au cœur, du cœur au NIC, du stockage à la mémoire, et retour. Les chiplets et l’empilement 3D reconnaissent dans l’industrie que la mémoire et l’interconnect sont l’événement principal.

C’est là que l’instinct SRE aide. Quand le package CPU devient un fabric, les goulots ressemblent à :

  • IPC élevé mais faible débit (attente mémoire ou contention sur verrous).
  • CPU peu occupé mais latence élevée (stalls, défauts de cache, mémoire distante).
  • Performance qui baisse après montée en charge (le trafic inter‑chiplet croît superlinéairement).

Ce qui change avec les chiplets et l’empilement

La localité mémoire n’est plus optionnelle. Sur un grand die monolithique, l’accès « distant » peut rester assez rapide. Sur des chiplets, un accès distant peut traverser plusieurs sauts de fabric et concurrencer d’autres trafics. Sur une SKU avec cache empilé, le cache « local » peut être plus grand mais la pénalité d’un miss peut être plus visible à cause d’un comportement fréquence/thermique modifié.

La bande passante n’est pas uniforme. Certains dies ont un accès plus proche à certains contrôleurs mémoire. Certains cœurs partagent des tranches de cache plus étroitement. La topologie peut récompenser une bonne ordonnancement et punir un ordonnancement naïf.

La variance de latence devient normale. Les états de gestion de puissance, le clock gating du fabric et les algorithmes de boost peuvent modifier les latences internes. Votre p99 le remarquera avant vos moyennes.

Thermique et consommation : le package est le nouveau champ de bataille

Sur le papier, vous achetez un CPU avec un TDP et une fréquence boost et vous appelez ça une journée. En réalité, les CPU modernes sont des systèmes gérés en puissance qui négocient constamment les fréquences selon la température, le courant et les caractéristiques de la charge. Les chiplets et les empilements 3D compliquent cette négociation.

Points chauds et gradients thermiques

Avec les chiplets, vous n’avez pas un profil thermique uniforme. Vous avez des points chauds où les cœurs sont denses, des dies IO séparés qui tournent plus frais, et parfois des dies empilés qui empêchent l’extraction de chaleur du die de calcul en dessous. Pour des charges de production longues, les fréquences soutenues comptent plus que les boosts de pointe.

Deux conséquences opérationnelles :

  • Les benchmarks mentent plus souvent. Les benchmarks courts atteignent le boost ; la production atteint l’état stationnaire et les limites de puissance.
  • Le refroidissement devient de la performance. Un dissipateur marginal ou un problème de flux d’air ne provoquera pas seulement un bridage ; il provoquera de la variance, plus difficile à déboguer.

Fiabilité : plus de connexions, plus d’endroits tristes

Plus de dies et plus d’interconnexions signifie plus de points potentiels de défaillance : micro‑bumps, TSVs, substrats de package et entraînement des liens. Les fournisseurs conçoivent pour cela, bien sûr. Mais sur le terrain, vous le verrez sous forme d’erreurs corrigées, de liens dégradés ou d’incidents « un hôte est bizarre ».

Un adage opérationnel utile, paraphrasant une idée provenant d’une voix notable de la fiabilité : Les systèmes complexes échouent de façons complexes ; réduisez les inconnues et mesurez les bonnes choses. (idée paraphrasée, inspirée de la pensée en ingénierie de la fiabilité souvent attribuée à John Allspaw)

Traduction : n’assumez pas l’uniformité entre hôtes, et n’assumez pas que deux sockets se comportent de la même façon juste parce que le SKU correspond.

Ce que cela signifie pour les SRE : performance, fiabilité et voisins bruyants

Vous n’avez pas besoin de devenir ingénieur packaging. Vous devez arrêter de traiter le « CPU » comme une ressource scalaire unique. Dans un monde chiplet + empilement, vous gérez :

  • Calcul topologique (les cœurs ne sont pas à égale distance de la mémoire et du cache)
  • Capacité d’interconnect (le fabric interne peut saturer)
  • Marge thermique (fréquences soutenues, bridage et p99)
  • Politique d’alimentation (capping, turbo et interactions avec l’ordonnanceur)

L’observabilité doit s’élargir

La surveillance hôte traditionnelle — %CPU, load average, mémoire utilisée — échouera de plus en plus à expliquer les goulots. Vous avez besoin d’au moins une prise en main basique sur :

  • Localité NUMA (les threads et la mémoire sont‑ils alignés ?)
  • Comportement de cache (misses LLC, pression de bande passante)
  • Fréquence et bridage (êtes‑vous limité par la puissance ?)
  • Placement par l’ordonnanceur (Kubernetes ou systemd a‑t‑il déplacé votre charge ?)

Oui, c’est pénible. Mais c’est moins pénible qu’un trimestre entier de « nous avons mis à jour les CPU et c’est devenu plus lent ».

Mode opératoire de diagnostic rapide : trouver le goulot en quelques minutes

Voici le flux de triage que j’utilise quand un service devient plus lent sur une nouvelle plateforme chiplet/empilée, ou devient plus lent après montée en charge. Le but n’est pas la cause racine parfaite. Le but est de prendre la bonne décision suivante rapidement.

Première étape : déterminer si vous êtes limité par le calcul, la mémoire ou le « fabric »

  1. Vérifier la fréquence CPU et le bridage : si les fréquences sont basses sous charge, vous êtes limité par la puissance/thermique.
  2. Vérifier la bande passante mémoire et la pression de miss de cache : si les misses LLC et la bande passante sont élevées, vous êtes limité par la mémoire.
  3. Vérifier la localité NUMA : si les accès mémoire distants sont élevés, vous êtes probablement lié à la topologie/ordonnanceur.

Deuxième étape : confirmer la topologie et le placement

  1. Vérifier les nœuds NUMA et le mapping CPU→nœud.
  2. Vérifier l’affinité CPU du processus et la politique mémoire.
  3. Vérifier si la charge saute entre nœuds (migrations par l’ordonnanceur).

Troisième étape : isoler une variable et relancer

  1. Épingler la charge à un nœud NUMA ; comparer p95/p99.
  2. Forcer l’allocation mémoire locale ; comparer le débit.
  3. Appliquer une politique d’alimentation conservative ; comparer la variance.

Si vous ne reproduisez pas de changement significatif en contrôlant placement et état de puissance, le problème est probablement au‑dessus (verrous, GC, IO) et vous devriez arrêter d’accuser le package CPU. Les CPU modernes sont compliqués, mais pas magiques.

Tâches pratiques avec commandes : quoi exécuter, ce que cela signifie, quelle décision prendre

Voici des tâches réelles à exécuter sur des hôtes Linux pour comprendre les comportements liés aux chiplets/empilements 3D. Les commandes sont volontairement ennuyeuses. Les outils ennuyeux vous gardent honnêtes.

Tâche 1 : Cartographier rapidement la topologie NUMA

cr0x@server:~$ lscpu | egrep 'Model name|Socket|Thread|Core|NUMA|CPU\(s\)'
CPU(s):                               128
Model name:                           AMD EPYC 9xx4
Thread(s) per core:                   2
Core(s) per socket:                   64
Socket(s):                            1
NUMA node(s):                         8

Ce que signifie la sortie : Vous avez 8 nœuds NUMA sur un seul socket. C’est une topologie type chiplet : plusieurs domaines mémoire et des sauts d’interconnect à l’intérieur d’un seul package.

Décision : Si la latence compte, prévoyez d’épingler les services critiques à l’intérieur d’un nœud NUMA et gardez la mémoire locale. L’ordonnancement par défaut peut être « correct », mais « correct » est ce qui tue le p99.

Tâche 2 : Voir quels CPU appartiennent à quel nœud NUMA

cr0x@server:~$ numactl --hardware
available: 8 nodes (0-7)
node 0 cpus: 0-15
node 0 size: 64000 MB
node 0 free: 61234 MB
node 1 cpus: 16-31
node 1 size: 64000 MB
node 1 free: 60110 MB
node 2 cpus: 32-47
node 2 size: 64000 MB
node 2 free: 59872 MB
node 3 cpus: 48-63
node 3 size: 64000 MB
node 3 free: 62155 MB
node 4 cpus: 64-79
node 4 size: 64000 MB
node 4 free: 60990 MB
node 5 cpus: 80-95
node 5 size: 64000 MB
node 5 free: 61801 MB
node 6 cpus: 96-111
node 6 size: 64000 MB
node 6 free: 61644 MB
node 7 cpus: 112-127
node 7 size: 64000 MB
node 7 free: 62002 MB

Ce que signifie la sortie : Chaque nœud NUMA possède une plage de CPU et un segment mémoire. Si votre processus tourne sur des CPU du nœud 0 mais alloue de la mémoire depuis le nœud 6, vous payez un péage de fabric à chaque accès distant.

Décision : Pour les services sensibles à la latence, alignez le pinning CPU et la politique mémoire. Pour les jobs de débit, vous pouvez préférer l’interleaving pour la bande passante.

Tâche 3 : Vérifier si le noyau enregistre des problèmes de localité NUMA

cr0x@server:~$ numastat -p 1 3
Per-node process memory usage (in MBs) for PID 1 (systemd)
Node 0 Node 1 Node 2 Node 3 Node 4 Node 5 Node 6 Node 7 Total
----- ----- ----- ----- ----- ----- ----- ----- -----
Numa_Hit      12     10      9      8      9     10      8      9    75
Numa_Miss      1      0      0      0      0      0      0      0     1
Numa_Foreign   0      0      0      0      0      0      0      0     0
Interleave_Hit 0      0      0      0      0      0      0      0     0
Local_Node    12     10      9      8      9     10      8      9    75
Other_Node     1      0      0      0      0      0      0      0     1

Ce que signifie la sortie : Pour PID 1 tout va bien. Pour votre vrai service, si Other_Node est élevé, vous payez des pénalités d’accès distant.

Décision : Si les accès distants sont élevés et que la latence tail est mauvaise, épinglez et localisez. Si votre objectif est le débit et que vous êtes limité par la bande passante, envisagez l’interleaving.

Tâche 4 : Vérifier le comportement de fréquence CPU sous charge

cr0x@server:~$ sudo turbostat --Summary --quiet --show CPU,Avg_MHz,Busy%,Bzy_MHz,PkgTmp,PkgWatt --interval 5
CPU  Avg_MHz  Busy%  Bzy_MHz  PkgTmp  PkgWatt
-    2850     62.10  4588     86      310.12

Ce que signifie la sortie : Les cœurs occupés tournent haut (Bzy_MHz), la température du package est élevée et la puissance est substantielle. Si Bzy_MHz s’effondre avec le temps tandis que Busy% reste élevé, vous êtes probablement limité par la puissance/thermique.

Décision : Pour les charges soutenues, ajustez le capping de puissance, le refroidissement ou réduisez la concurrence. Ne poursuivez pas les chiffres de boost d’un seul run.

Tâche 5 : Confirmer que la politique d’alimentation (governor) ne vous sabote pas

cr0x@server:~$ cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
performance

Ce que signifie la sortie : Le governor est réglé sur performance. S’il est sur powersave sur un hôte sensible à la latence, vous demandez essentiellement de la gigue.

Décision : Définissez une politique appropriée par rôle de cluster. Un cluster batch peut économiser de l’énergie ; un cluster OLTP ne doit pas se faire passer pour un portable.

Tâche 6 : Mesurer les migrations d’ordonnanceur (un tueur NUMA silencieux)

cr0x@server:~$ pidstat -w -p $(pgrep -n myservice) 1 5
Linux 6.5.0 (server)  01/12/2026  _x86_64_  (128 CPU)

01:10:01 PM   UID       PID   cswch/s nvcswch/s  Command
01:10:02 PM  1001     43210   120.00     15.00  myservice
01:10:03 PM  1001     43210   135.00     20.00  myservice
01:10:04 PM  1001     43210   128.00     18.00  myservice

Ce que signifie la sortie : Les context switches sont modérés. Si vous voyez aussi des migrations CPU fréquentes (via perf ou schedstat), vous pouvez perdre la localité de cache entre chiplets.

Décision : Considérez l’épinglage CPU pour les threads les plus chauds, ou ajustez votre runtime (threads GC, nombre de workers) pour réduire le churn.

Tâche 7 : Vérifier la pression de bande passante mémoire avec pcm-memory (si installé)

cr0x@server:~$ sudo pcm-memory 1 -csv
Time,Ch0Read,Ch0Write,Ch1Read,Ch1Write,SystemRead,SystemWrite
1.00,12.3,5.1,11.8,4.9,198.4,82.1
2.00,12.5,5.0,12.1,4.8,201.0,80.9

Ce que signifie la sortie : La bande passante lecture/écriture système est élevée. Si elle est proche des limites de la plateforme pendant votre incident, vous êtes limité par la mémoire, pas par le CPU.

Décision : Réduisez le trafic mémoire : corrigez la disposition des données, réduisez les copies, augmentez le hit rate du cache, ou migrez vers une plateforme avec cache empilé/HBM si votre jeu de travail y correspond.

Tâche 8 : Observer les signaux de cache‑miss et stalls avec perf

cr0x@server:~$ sudo perf stat -p $(pgrep -n myservice) -e cycles,instructions,cache-misses,branches,branch-misses -a -- sleep 10
 Performance counter stats for 'system wide':

    38,112,001,220      cycles
    52,880,441,900      instructions              #    1.39  insn per cycle
       902,110,332      cache-misses
     9,221,001,004      branches
       112,210,991      branch-misses

      10.002113349 seconds time elapsed

Ce que signifie la sortie : Beaucoup de cache misses. L’IPC est correct, mais les misses peuvent toujours dominer le temps mur selon la charge. Sur les CPU chiplet, les misses peuvent se traduire en trafic de fabric et en accès mémoire distants.

Décision : Si les misses de cache corrèlent avec des pics de latence, priorisez la localité : épinglez les threads, réduisez la contention sur l’état partagé et testez des SKUs avec cache empilé quand le jeu de travail est juste au‑dessus du LLC.

Tâche 9 : Vérifier les erreurs mémoire et les tempêtes d’erreurs corrigées

cr0x@server:~$ sudo ras-mc-ctl --summary
Memory controller events summary:
  Corrected errors: 24
  Uncorrected errors: 0
  No DIMM labels were found

Ce que signifie la sortie : Des erreurs corrigées existent. Une hausse du taux peut causer une dégradation de performance et un comportement imprévisible, et sur des plateformes de packaging avancé vous voulez le remarquer tôt.

Décision : Si les erreurs corrigées augmentent, planifiez une maintenance : reseat, remplacer les DIMMs, mettre à jour le firmware ou retirer l’hôte du service. N’attendez pas que les erreurs non corrigées vous apprennent l’humilité.

Tâche 10 : Valider la santé des liens/PCIe (le die IO fait partie de l’histoire)

cr0x@server:~$ sudo lspci -vv | sed -n '/Ethernet controller/,+25p' | egrep 'LnkSta:|LnkCap:'
LnkCap: Port #0, Speed 16GT/s, Width x16
LnkSta: Speed 16GT/s (ok), Width x16 (ok)

Ce que signifie la sortie : Le lien fonctionne à la vitesse/largeur attendue. Si vous voyez des liens downtrainés, la performance IO baisse et des cycles CPU sont gaspillés en surcharge d’interrupts/paquets.

Décision : Les liens downtrainés déclenchent : vérifiez les risers, les réglages BIOS, le firmware et l’assise physique. N’« optimisez » pas le logiciel autour d’un matériel cassé.

Tâche 11 : Confirmer la distribution des interruptions (éviter les accumulations IRQ sur un seul cœur)

cr0x@server:~$ cat /proc/interrupts | egrep 'eth0|mlx|ens' | head
  55:   10223342          0          0          0   IR-PCI-MSI 524288-edge      ens3f0-TxRx-0
  56:          0    9981221          0          0   IR-PCI-MSI 524289-edge      ens3f0-TxRx-1
  57:          0          0    9875522          0   IR-PCI-MSI 524290-edge      ens3f0-TxRx-2

Ce que signifie la sortie : Les interruptions sont réparties sur les CPU. Si toutes les interruptions arrivent sur un CPU d’un nœud NUMA tandis que votre charge tourne ailleurs, vous aurez du trafic inter‑nœud et de la gigue.

Décision : Épinglez les IRQ proches du nœud NUMA du NIC et des threads de service qui consomment les paquets. La localité s’applique aussi à l’IO.

Tâche 12 : Vérifier la politique mémoire et exécuter un test local explicite

cr0x@server:~$ numactl --cpunodebind=2 --membind=2 ./bench --duration 30
throughput=118223 ops/s
p99_latency_ms=3.4

Ce que signifie la sortie : Vous avez forcé CPU et mémoire sur le nœud 2. Comparez cela aux résultats non épinglés. Un large delta indique des pénalités NUMA/fabric.

Décision : Si l’épinglage améliore significativement le p99, implémentez le placement (CPUAffinity systemd, topology manager Kubernetes, ou pinning côté application) plutôt que de chasser des micro‑optimisations.

Tâche 13 : Inspecter les hugepages et les indicateurs de pression TLB

cr0x@server:~$ grep -E 'HugePages_Total|HugePages_Free|Hugepagesize' /proc/meminfo
HugePages_Total:    4096
HugePages_Free:     3900
Hugepagesize:       2048 kB

Ce que signifie la sortie : Les hugepages sont disponibles. Sur des charges intensives en mémoire, les hugepages peuvent réduire les misses TLB, ce qui compte davantage quand la latence mémoire est déjà plus haute à cause des accès distants.

Décision : Si le profilage montre une pression TLB, activez les hugepages et validez l’impact. Ne faites pas de cargo‑culting — mesurez.

Tâche 14 : Détecter le bridage et les raisons de limite de puissance (exemple Intel via RAPL)

cr0x@server:~$ dmesg | egrep -i 'thrott|powercap|rapl' | tail -n 5
[ 8123.221901] intel_rapl: power limit changed to 210W
[ 8123.222110] CPU0: Package power limit exceeded, capping frequency

Ce que signifie la sortie : Le système applique un power‑cap. Votre benchmark a pu tourner avant le cap ; les runs de production s’exécutent pendant celui‑ci.

Décision : Alignez les réglages BIOS/firmware de puissance avec l’intention de la charge. Si vous limitez pour des budgets d’énergie du datacenter, ajustez les SLO et peaufinez la concurrence.

Trois mini‑histoires d’entreprise de l’ère des chiplets

Mini‑histoire 1 : L’incident causé par une mauvaise hypothèse

Une entreprise SaaS de taille moyenne a migré une couche API sensible à la latence vers de nouveaux serveurs. Même nombre de cœurs qu’avant, fréquences de boost annoncées plus élevées, et un chiffre L3 massif qui semblait de l’argent gratuit. Le déploiement était prudent : 5 % en canary, les métriques semblaient correctes, puis 25 %, puis 50 %.

À environ la moitié de la flotte, la latence p99 a commencé à fluctuer. Pas une montée régulière — des fluctuations. Les graphiques formaient une dent de scie qui a fait argumenter sur les patterns de trafic et le GC. L’utilisation CPU restait modérée. Le réseau semblait propre. Le stockage était calme. Le canal d’incident s’est rempli de la pire phrase des opérations : « Rien n’a l’air anormal. »

La mauvaise hypothèse : ils ont traité le CPU comme uniforme et supposé que si le %CPU moyen était correct, le CPU n’était pas le goulot. En réalité, la charge était ordonnancée à travers des nœuds NUMA et allouait fréquemment de la mémoire à distance à cause du comportement d’allocation du runtime et de la liberté du scheduler de conteneurs à déplacer les tâches. Les accès distants n’étaient pas catastrophiques ; ils étaient variables, ce qui a détruit la latence tail.

Ils l’ont prouvé en épinglant le service à un seul nœud NUMA et en forçant l’allocation locale en test. Le p99 s’est stabilisé immédiatement et la dent de scie a disparu. Le correctif n’était pas glamour : ordonnanceur conscient de la topologie, épinglage CPU pour les pods les plus chauds et une politique mémoire délibérée. Ils ont aussi cessé de sur‑consolider pods sensibles à la latence et batch sur le même socket. « Plus d’utilisation » n’était pas l’objectif ; la latence prévisible l’était.

Mini‑histoire 2 : L’optimisation qui s’est retournée contre eux

Une fintech exécutait un moteur de risque qui scannait un grand jeu de données en mémoire de façon répétée. Ils ont acheté une SKU CPU avec cache empilé parce qu’un benchmark fournisseur montrait un gros gain. Les premiers tests étaient prometteurs. Le débit s’est amélioré. Tout le monde a célébré. Puis ils ont fait ce que font les entreprises : ils ont « optimisé ».

L’équipe a augmenté la parallélisme agressivement, supposant que le cache supplémentaire maintiendrait l’échelle. Ils ont aussi activé une politique turbo plus agressive dans le BIOS pour chasser des accélérations de courte durée. En staging, la charge finissait plus vite — la plupart du temps.

En production, l’optimisation a échoué de deux manières. D’abord, les threads supplémentaires ont augmenté le trafic cross‑chiplet parce que la charge avait une structure partagée mal partitionnée. L’interconnect est devenu congestionné. Ensuite, la politique turbo a élevé rapidement les températures, provoquant un bridage thermique en plein run. Le système n’a pas seulement ralenti ; il est devenu imprévisible. Certains runs terminaient vite ; d’autres frappaient le bridage et traînaient.

Le correctif a été presque ennuyant : réduire le parallélisme pour maintenir la localité, partitionner le dataset plus soigneusement et définir une politique d’alimentation optimisée pour la fréquence soutenue plutôt que le boost de pointe. Le cache empilé a encore aidé — mais seulement quand le logiciel respectait la topologie et l’enveloppe thermique. La leçon : plus de cache n’excuse pas un mauvais comportement d’échelle.

Mini‑histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise

Une grande équipe plateforme d’entreprise a standardisé une « checklist de bring‑up hardware » pour les nouvelles générations de CPU. Elle incluait des baselines BIOS/firmware, versions de microcode, vérification de la topologie NUMA et un ensemble fixe de tests smoke perf/latence épinglés à des nœuds spécifiques.

Quand un lot de nouveaux serveurs est arrivé, les smoke tests ont montré une régression subtile : la bande passante mémoire était plus faible que prévu sur un nœud NUMA, et la latence p99 sous une charge mixte synthétique était pire. Rien ne tombait en erreur. La plupart des équipes auraient déclaré « dans la variance » et seraient passées à autre chose.

La checklist a forcé l’escalade. Il s’est avéré qu’un réglage BIOS lié à l’interleaving mémoire et à la gestion de puissance différait de la baseline à cause d’un changement de défaut fournisseur. Les serveurs fonctionnaient techniquement, juste pas de la même façon que le reste de la flotte. Ce mismatch serait devenu un cauchemar d’astreinte plus tard, car un comportement hétérogène dans un groupe autoscalé transforme les incidents en jeux de probabilité.

Ils ont corrigé la baseline, réimagé les hôtes, relancé les mêmes tests épinglés et obtenu les résultats attendus. Pas de héros. Pas d’incident nocturne. Juste de la discipline opérationnelle : mesurer, standardiser et refuser d’accepter la variance silencieuse dans un monde où les packages sont de petits systèmes distribués.

Erreurs courantes : symptômes → cause racine → correctif

1) Symptôme : pics de latence p99 après montée en nombre de cœurs

Cause racine : contention cross‑chiplet et augmentation des accès mémoire distants quand les threads se répartissent sur des nœuds NUMA ; les structures de données partagées amplifient le trafic.

Correctif : partitionner l’état, réduire le partage inter‑threads, épingler les workers critiques dans un nœud NUMA et utiliser un ordonnancement conscient de la topologie.

2) Symptôme : utilisation CPU modérée mais faible débit

Cause racine : stalls mémoire (misses LLC, latence DRAM), congestion du fabric ou migrations fréquentes se cachent derrière « pas occupé ».

Correctif : utilisez perf stat et des outils de bande passante mémoire ; vérifiez numastat ; épinglez et localisez ; réduisez le churn de l’allocateur et les copies.

3) Symptôme : nouveaux serveurs plus rapides en benchmark mais pires en production

Cause racine : les benchmarks frappent les boosts et les états de cache chauds ; la production atteint les limites de puissance soutenue et des charges mixtes.

Correctif : testez avec des runs en état stationnaire, incluez des métriques p99 et validez sous concurrence et conditions thermiques réalistes.

4) Symptôme : un hôte dans une flotte est constamment bizarre

Cause racine : lien PCIe downtrainé, canal mémoire dégradé, tempêtes d’erreurs corrigées ou dérive BIOS affectant puissance/topologie.

Correctif : vérifiez lspci -vv, résumés RAS, versions microcode/BIOS ; mettez en quarantaine et remédiez plutôt que d’ajuster autour du problème.

5) Symptôme : gigue de latence après activation de fonctionnalités d’économies d’énergie

Cause racine : états C agressifs, clock gating du fabric, scaling de fréquence ou limites de puissance du package causent des comportements de réveil/boost variables.

Correctif : utilisez un governor performance pour les niveaux latence, ajustez les états power BIOS et validez avec turbostat sous charge réelle.

6) Symptôme : débit PPS réseau en baisse après rafraîchissement hardware

Cause racine : IRQs et threads sur différents nœuds NUMA ; l’IO die et la localité NIC comptent, et le trafic inter‑nœud ajoute de la latence.

Correctif : alignez l’affinité IRQ et les threads d’application sur le nœud NUMA du NIC ; confirmez la largeur/vitesse du lien ; évitez la sur‑consolidation.

7) Symptôme : « On a ajouté du cache empilé mais aucun gain »

Cause racine : le jeu de travail ne rentre pas, ou la charge est limitée par la bande passante plutôt que par la latence de cache ; le gain est spécifique à la charge.

Correctif : profilez les taux de miss de cache et la bande passante ; testez des tailles de données représentatives ; envisagez HBM ou des changements algorithmiques si vous êtes lié par la bande passante.

8) Symptôme : après containerisation, performance régressée sur CPU chiplet

Cause racine : le scheduler de conteneurs a déplacé les threads à travers CPU/nœuds NUMA ; les quotas cgroup CPU ont introduit de la variabilité ; la localité du page cache s’est aggravée.

Correctif : utilisez CPU manager/topology manager, définissez correctement requests/limits, et épinglez les pods gourmands en mémoire sur des nœuds NUMA.

Listes de contrôle / plan étape par étape pour nouvelles plateformes

Plan étape par étape : mettre en production une nouvelle plateforme chiplet/empilée

  1. Topologie de base : enregistrez lscpu et numactl --hardware pour le SKU ; stockez‑les avec vos artefacts de build.
  2. Standardiser le firmware : réglages BIOS, microcode et politiques de puissance doivent être cohérents dans le pool.
  3. Choisir une posture de puissance par tier : les clusters latence ont la politique performance ; les clusters batch peuvent être intentionnellement power‑cappés.
  4. Exécuter des smoke tests épinglés : mesurez débit et p99 avec CPU+mémoire liés à un nœud ; puis exécutez non épinglé ; comparez les deltas.
  5. Valider la marge de bande passante mémoire : si votre charge est liée à la mémoire, la planification de capacité devient planification de bande passante.
  6. Valider la localité IO : vérifier la santé des liens PCIe et la distribution des IRQ ; assurer que l’affinité NIC correspond au placement CPU.
  7. Décider d’une politique de placement : soit adopter NUMA (épingler et localiser), soit interleaver explicitement pour la bande passante. Ne faites pas d’« hybride accidentel ».
  8. Déployer avec détection de variance : surveillez non seulement les médianes mais la dispersion entre hôtes ; alertez tôt sur « un hôte bizarre ».
  9. Documenter les modes de défaillance : signatures de bridage, seuils d’erreurs corrigées et comment mettre un hôte en quarantaine.
  10. Retester après mises à jour du noyau : les changements d’ordonnanceur peuvent aider ou nuire à la gestion de la topologie ; validez périodiquement.

Checklist : choisir entre cache empilé et plus de bande passante mémoire

  • Si votre jeu de travail dépasse légèrement le LLC et que vous voyez beaucoup de misses LLC : le cache empilé peut être un gros gain.
  • Si la bande passante mémoire est proche du maximum et que les stalls dominent : le cache empilé ne vous sauvera peut‑être pas ; priorisez la bande passante (plateformes HBM, plus de channels) ou réduisez le trafic.
  • Si la latence tail compte : préférez des solutions qui réduisent la variance (localité, politique de puissance stable) plutôt que le pic brut.

Checklist : quoi éviter en adoptant des CPU à forte charge de chiplets

  • Ne supposez pas « un socket = uniforme. » Mesurez le comportement NUMA.
  • Ne tolérez pas la dérive BIOS dans un groupe autoscalé.
  • Ne peaufinez pas les applications sans d’abord vérifier le comportement de puissance et de bridage.
  • Ne mélangez pas workloads latence et batch sur le même socket à moins d’avoir une isolation stricte.

FAQ

1) Les chiplets sont-ils toujours plus rapides que les dies monolithiques ?

Non. Les chiplets sont principalement une stratégie économique et de vélocité produit, avec des bénéfices de performance quand l’interconnect et la topologie sont bien gérés. Une mauvaise localité peut effacer le gain.

2) L’empilement 3D fera‑t‑il chauffer davantage les CPU ?

Souvent, oui en pratique. Les stacks peuvent empêcher l’évacuation de la chaleur et créer des points chauds. Les fournisseurs conçoivent autour de cela, mais des charges soutenues peuvent voir un bridage plus précoce ou plus de variance.

3) Le tuning NUMA est‑il désormais obligatoire ?

Pour les services sensibles à la latence sur des CPU à forte charge de chiplets, c’est presque obligatoire. Pour du batch parfaitement parallèle, vous pouvez souvent vous en passer — jusqu’à ce que vous ne puissiez plus.

4) Quels workloads bénéficient le plus du cache empilé ?

Les charges avec un jeu de travail plus grand que le cache normal mais plus petit que des patterns streaming DRAM‑friendly : workloads clé‑valeurs chauds, certaines analyses, certaines simulations et structures de données en mémoire majoritairement en lecture.

5) Quel est le risque opérationnel d’un packaging avancé ?

Plus de composants et de liens peuvent signifier plus de dégradations subtiles : tempêtes d’erreurs corrigées, downtraining de liens ou variance plateforme. Vos pratiques de monitoring et de quarantaine comptent davantage.

6) Les chiplets signifient‑ils que « plus de cœurs » va cesser d’aider ?

Plus de cœurs aideront toujours pour les charges parallèles, mais l’échelle devient plus sensible à la bande passante mémoire, la congestion interconnect et la contention d’état partagé. Les gains faciles sont partis.

7) Comment HBM change la planification de capacité ?

HBM vous pousse vers un modèle en niveaux : très haute bande passante mais capacité limitée. Planifiez ce qui doit rester en HBM, ce qui peut déborder sur la DDR, et comment votre allocateur/runtime se comporte.

8) UCIe va‑t‑il rendre les packages CPU modulaires comme des blocs PC ?

Finalement, plus modulaires qu’aujourd’hui — mais n’attendez pas du plug‑and‑play. L’intégrité du signal, l’alimentation, les thermiques et la validation restent difficiles, et la « norme » n’éliminera pas la physique.

9) Quel est le changement le plus simple « assez bon » pour réduire la latence tail sur CPU chiplet ?

Épinglez vos threads les plus chauds à un nœud NUMA et gardez leur mémoire locale. Puis vérifiez avec un test A/B épinglé. Si ça aide, investissez dans un ordonnancement conscient de la topologie.

10) Dois‑je acheter des SKUs avec cache empilé pour tout ?

Non. Achetez‑les pour les workloads qui démontrent une sensibilité au cache lors du profilage. Sinon vous paierez du silicium qui ne fera que garnir votre tableau de procurement.

Étapes pratiques suivantes

L’empilement 3D et les chiplets ne sont pas une mode ; ils dessinent la route à venir. Le CPU devient un système distribué au niveau package avec des contraintes thermiques et topologiques. Vos logiciels et vos opérations doivent s’y adapter.

Ce qu’il faut faire la semaine prochaine (pas le prochain trimestre)

  1. Choisissez un service avec SLOs de latence et exécutez le test NUMA épinglé vs non épinglé (numactl) pour quantifier la sensibilité.
  2. Ajoutez deux panneaux au niveau hôte : fréquence/bridage CPU (dérivé de turbostat) et accès NUMA distant (numastat/PMU si disponible).
  3. Standardisez les baselines BIOS/microcode pour chaque pool matériel ; alertez sur la dérive.
  4. Rédigez un runbook d’une page utilisant le Mode opératoire de diagnostic rapide ci‑dessus pour que l’astreinte n’accuse pas le réseau par réflexe.
  5. Décidez votre philosophie de placement : priorité à la localité pour les tiers latence ; interleave/bande passante pour les tiers débit — puis appliquez‑la.

Si vous ne faites rien d’autre, faites ceci : arrêtez de traiter le %CPU comme la vérité. Sur les designs chiplet et empilés, le %CPU est une impression. Mesurez la localité, mesurez la bande passante et mesurez le bridage. Ensuite vous pourrez argumenter avec confiance, et c’est le seul type de dispute que les opérations peuvent se permettre.

Proxmox « impossible d’allouer la mémoire » : ballooning, surcommit et comment le régler

Vous cliquez sur Start d’une VM et Proxmox répond par l’équivalent numérique d’un haussement d’épaules :
« impossible d’allouer la mémoire ». Ou pire, la VM démarre puis l’hôte commence à tuer des processus au hasard
comme un régisseur stressé dans un théâtre avec une seule sortie.

Les échecs mémoire dans Proxmox ne sont pas mystiques. Ce sont des problèmes de comptabilité : ce que l’hôte pense avoir,
ce que les VM prétendent pouvoir utiliser, ce qu’elles touchent réellement, et ce que le noyau est prêt
à promettre à cet instant. Corrigez la comptabilité, et la plupart du drame disparaît.

Guide de diagnostic rapide

Si vous êtes d’astreinte et que le cluster hurle, vous ne voulez pas une leçon de philosophie. Vous voulez une boucle serrée :
confirmer le mode d’échec, identifier le facteur limitant, faire un changement sûr, répéter.

Premier point : s’agit-il d’une exhaustion mémoire de l’hôte ou d’une limite par VM ?

  • Si la VM échoue au démarrage avec impossible d’allouer la mémoire, suspectez les limites de commit de l’hôte,
    les limites de cgroup, les hugepages, ou la fragmentation — souvent visible immédiatement dans dmesg / journal.
  • Si la VM démarre puis est tuée, il s’agit généralement du tueur OOM du guest (à l’intérieur de la VM) ou
    du tueur OOM de l’hôte (tuant QEMU), selon les journaux qui montrent la mise à mort.

Deuxième point : vérifiez la marge réelle de l’hôte, pas les jolis graphiques

  • Memoire libre et swap de l’hôte : free -h
  • Pression mémoire et blocages de reclaim : vmstat 1
  • Preuves d’OOM : journalctl -k et dmesg -T
  • Taille de l’ARC ZFS (si vous utilisez ZFS) : arcstat / /proc/spl/kstat/zfs/arcstats

Troisième point : vérifiez la configuration et la politique côté Proxmox

  • Configuration VM : cible ballooning vs max, hugepages, NUMA, etc. :
    qm config <vmid>
  • Politique de surcommit du nœud : pvesh get /nodes/<node>/status et
    /etc/pve/datacenter.cfg
  • Si c’est un container (LXC), vérifiez la limite mémoire du cgroup et la limite de swap :
    pct config <ctid>

Quatrième point : choisissez la mitigation immédiate la moins mauvaise

  • Arrêtez une VM non critique pour libérer de la RAM et réduire la pression maintenant.
  • Si ZFS mange la machine : limitez l’ARC (de façon persistante) ou redémarrez en dernier recours.
  • Si vous êtes en surcommit : réduisez la mémoire max des VM (pas seulement la cible balloon).
  • Si le swap est absent et que vous êtes serré : ajoutez du swap (hôte) pour éviter un OOM instantané pendant que vous corrigez le dimensionnement.

Blague #1 : Le surcommit mémoire, c’est comme la comptabilité d’entreprise — tout fonctionne jusqu’à ce que tout le monde essaie de faire passer le déjeuner la même journée.

Ce que signifie réellement « impossible d’allouer la mémoire » dans Proxmox

Proxmox est une couche de gestion. L’allocateur réel est Linux, et pour les VM c’est généralement QEMU/KVM. Lorsque vous voyez
impossible d’allouer la mémoire, l’une de ces situations se produit :

  • QEMU ne peut pas réserver la RAM demandée par la VM au moment du démarrage. Cela peut échouer même si
    free semble correct, car Linux applique des règles de commit et la fragmentation.
  • Le noyau refuse l’allocation à cause de la logique overcommit/CommitLimit. Linux suit combien de mémoire les processus ont promis de potentiellement utiliser (mémoire virtuelle), et il peut refuser de nouvelles promesses.
  • Des hugepages sont demandées mais non disponibles. Les hugepages sont pré-allouées. Si elles n’existent pas,
    l’allocation échoue immédiatement et bruyamment.
  • Des limites de cgroup bloquent l’allocation. Plus courant avec les conteneurs, mais peut s’appliquer si systemd slices ou cgroups personnalisés sont impliqués.
  • La mémoire est disponible mais pas dans la forme demandée. La fragmentation peut empêcher de grosses allocations contiguës, surtout avec les hugepages ou certains besoins DMA.

Pendant ce temps, la « solution » que certains proposent — le ballooning — ne change pas ce que QEMU a demandé si vous avez toujours configuré une grande mémoire
max. Le ballooning ajuste ce que le guest est encouragé à utiliser, pas ce que l’hôte doit être prêt à garantir au pire moment.

Deux nombres importent : cible du guest et max du guest

Dans les options VM de Proxmox, le ballooning vous donne :

  • Mémoire (max) : le plafond de la VM. QEMU réserve une comptabilité pour cela.
  • Balloon (min/target) : la cible d’exécution de la VM qui peut être réduite sous pression.

Si vous définissez le max à 64 Go « au cas où » et la cible balloon à 8 Go « parce qu’elle est généralement inactive », vous avez dit à l’hôte :
« Veuillez être prêt à financer mon style de vie à 64 Go. » L’hôte, étant raisonnable, peut dire non.

Faits intéressants et un peu d’histoire (pour que vous arrêtiez de le répéter)

  1. Le comportement d’overcommit de Linux est ancien et intentionnel : il existe parce que beaucoup d’allocations ne sont jamais entièrement utilisées,
    et une comptabilité stricte gaspillerait de la RAM pour des promesses vides.
  2. Le tueur OOM précède la plupart des piles de virtualisation modernes ; c’était la réponse pragmatique de Linux à « quelqu’un ment sur la mémoire »
    bien avant que le marketing cloud transforme le mensonge en fonctionnalité.
  3. Le ballooning est devenu courant avec les hyperviseurs précoces parce que les guests inactifs accaparaient le cache et rendaient la consolidation pire qu’elle n’était.
  4. KSM (Kernel Samepage Merging) a été conçu pour dédupliquer des pages mémoire identiques entre VM — particulièrement utile quand
    de nombreuses VM exécutent la même image OS.
  5. Les Transparent Huge Pages (THP) ont été introduites pour améliorer les performances en utilisant automatiquement de plus grandes pages, mais
    elles peuvent provoquer des pics de latence sous pression mémoire à cause du travail de compaction.
  6. L’ARC de ZFS n’est pas « juste un cache ». Il concurrence la mémoire anonyme. Si vous ne le limitez pas, il prendra volontiers la RAM
    jusqu’à ce que le noyau le force à en libérer — parfois trop tard.
  7. Les cgroups ont changé la donne : au lieu que l’hôte soit une grande famille heureuse, les limites mémoire peuvent maintenant faire échouer
    une seule VM ou un seul conteneur alors que l’hôte a l’air correct.
  8. Le swap était autrefois un conseil obligatoire ; puis les gens l’ont abusé ; puis on l’a rejeté ; puis les SSD modernes ont rendu
    « un petit swap contrôlé » à nouveau pertinent dans de nombreux cas.

Une citation opérationnelle qui reste douloureusement pertinente (idée paraphrasée) : Werner Vogels a dit que le cœur de la fiabilité est d’attendre les pannes et de concevoir pour elles, pas de prétendre qu’elles n’arriveront pas.

Ballooning : ce que ça fait, ce que ça ne fait pas, et pourquoi il vous trompe

Ce qu’est réellement le ballooning

Le ballooning utilise un pilote à l’intérieur du guest (virtio-balloon typiquement). L’hôte demande au guest d’« gonfler » un ballon,
c’est-à-dire : allouer de la mémoire à l’intérieur du guest et la pinner pour que le guest ne puisse pas l’utiliser. Cette mémoire devient récupérable
du point de vue de l’hôte parce que le guest l’a volontairement cédée.

C’est intelligent. C’est aussi limité par la physique et le comportement du guest :

  • Si le guest est sous réelle pression mémoire, il ne peut pas vous céder grand-chose sans swapper ou s’auto-oomer.
  • Si le guest n’a pas le pilote balloon, le ballooning est essentiellement une danse interprétative.
  • Le ballooning est réactif. Si l’hôte est déjà en difficulté, vous pouvez être trop tard.

Le piège important du ballooning dans Proxmox

La configuration de ballooning dans Proxmox donne souvent un faux sentiment de sécurité. Les gens définissent des cibles balloon basses et des max mémoire élevées,
pensant « n’utiliser que la cible ». Mais la comptabilité de QEMU et la logique de commit du noyau doivent souvent tenir compte du maximum.

Position opérationnelle : le ballooning est un outil d’ajustement, pas une excuse pour éviter le dimensionnement. Utilisez-le pour des charges élastiques
où l’OS invité peut s’adapter. Ne l’utilisez pas comme stratégie principale pour surcharger un hôte jusqu’au grincement.

Quand le ballooning en vaut la peine

  • Clusters de dev/test où les guests sont inactifs et les pics sont rares et tolérables.
  • Parcs VDI avec beaucoup de VMs similaires, souvent combinés avec KSM.
  • Flottes de serveurs génériques où vous pouvez appliquer des max raisonnables, pas des valeurs de fantaisie.

Quand le ballooning est un piège

  • Bases de données avec latence stricte et pools de buffers (la pression mémoire du guest devient pression IO).
  • Systèmes avec swap désactivé dans les guests (le ballooning peut provoquer un OOM à l’intérieur du guest).
  • Hôtes déjà serrés en mémoire où le temps de réaction du ballooning est trop lent.

Surcommit : quand c’est judicieux, quand c’est imprudent

Trois « surcommits » différents que les gens confondent

En pratique, vous jonglez avec trois couches :

  1. Surcommit de planification/comptabilité Proxmox : si Proxmox autorise ou non le démarrage d’une VM
    selon la mémoire configurée, les cibles balloon et la mémoire du nœud.
  2. Surcommit de mémoire virtuelle Linux : vm.overcommit_memory et CommitLimit.
  3. Surcommit physique réel : si la somme de la mémoire activement utilisée par les guests dépasse la RAM de l’hôte
    (et si vous avez swap, compression, ou un plan).

Comptabilité de commit Linux en un paragraphe opérationnel

Linux décide d’autoriser une allocation en se basant sur combien de mémoire pourrait être utilisée si les processus la touchaient.
Ce chiffre « pourrait être utilisé » est suivi comme Committed_AS. Le plafond autorisé est CommitLimit,
à peu près RAM + swap moins quelques réserves, modifié par les réglages d’overcommit. Si Committed_AS approche
CommitLimit, le noyau commence à refuser les allocations — bonjour, « impossible d’allouer la mémoire ».

Conseils orientés

  • Production : gardez le surcommit modéré et appliquez des max VM réalistes. Si vous ne pouvez pas indiquer votre
    ratio de surcommit et votre plan d’éviction, vous ne surcommettez pas — vous jouez.
  • Lab : surcommettez agressivement si vous acceptez des événements OOM occasionnels. Étiquetez-le honnêtement et cessez de prétendre que c’est de la prod.
  • Charges mixtes : séparez les consommateurs de mémoire bruyants (DB, analytics) sur leurs propres nœuds,
    ou imposez-leur des plafonds stricts. « Coexistence » est le mot que l’on utilise juste avant le post-mortem.

ZFS ARC, cache de pages et la mémoire hôte que vous avez oubliée de budgéter

Proxmox tourne souvent sur ZFS car les snapshots et send/receive sont addictifs. Mais ZFS n’est pas timide : il utilisera la RAM
pour l’ARC (Adaptive Replacement Cache). C’est génial jusqu’à ce que ça ne le soit plus.

ARC versus « mémoire libre »

L’ARC est récupérable, mais pas instantanément et pas toujours de la façon dont votre démarrage de VM le souhaite. Sous pression, le noyau
essaie de récupérer le page cache et l’ARC, mais si vous êtes dans une boucle serrée d’allocations (démarrage d’une VM, inflation de mémoire,
fork de processus), vous pouvez rencontrer des échecs transitoires.

Que faire

  • Sur des hôtes ZFS avec beaucoup de VM, fixez un maximum d’ARC sensé (zfs_arc_max). Ne laissez pas l’ARC « se battre » contre vos guests.
  • Traitez la mémoire hôte comme une infrastructure partagée. L’hôte a besoin de mémoire pour :
    noyau, slab, réseau, métadonnées ZFS, overhead QEMU, et vos agents de monitoring qui jurent qu’ils sont légers.

Swap : pas un péché, mais pas non plus un plan de vie

Pas de swap signifie que vous avez retiré les amortisseurs. Avec la virtualisation, cela peut être fatal car un pic de pression
se transforme en tueries OOM immédiates au lieu d’une dégradation lente et diagnostiquable.

Mais le swap peut aussi devenir un bourbier de performances. L’objectif est un swap contrôlé : assez pour survivre aux pics, pas assez pour
masquer un surcommit chronique.

Recommandations swap pour l’hôte (pratique, pas dogmatique)

  • Si vous utilisez ZFS et de nombreuses VM : ajoutez du swap. Même une quantité modérée peut empêcher l’hôte de tuer
    QEMU lors de pics brefs.
  • Si votre stockage est lent : gardez le swap plus petit et priorisez un bon dimensionnement de la RAM. Swapper sur un RAID HDD occupé
    n’est pas « stabilité », c’est « souffrance prolongée ».
  • Si vous utilisez SSD/NVMe : le swap est beaucoup plus tolérable, mais ce n’est toujours pas gratuit. Surveillez les taux de swap-in/out,
    pas seulement l’espace de swap utilisé.

Blague #2 : Le swap, c’est comme une réunion qui aurait pu être un e-mail — parfois ça sauve la journée, mais si vous y vivez, votre carrière est finie.

Tâches pratiques : commandes, sorties et décisions (12+)

Voici les vérifications que j’exécute réellement quand un nœud Proxmox commence à lancer des erreurs d’allocation mémoire. Chaque tâche inclut :
une commande, une sortie d’exemple, ce que cela signifie, et quelle décision cela entraîne.

Task 1: Check host RAM and swap at a glance

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:            62Gi        54Gi       1.2Gi       2.3Gi       6.9Gi       2.8Gi
Swap:            8Gi       1.6Gi       6.4Gi

Sens : « available » est votre marge à court terme avant que le reclaim ne devienne pénible. 2.8 GiB sur un hôte de 62 GiB
avec de la virtualisation est serré mais pas immédiatement condamné.

Décision : Si available est < 1–2 GiB et que des VM échouent au démarrage, arrêtez des VM non critiques maintenant.
Si le swap est 0, ajoutez du swap comme stabilisateur pendant que vous corrigez le dimensionnement.

Task 2: Identify if the kernel is rejecting allocations due to commit limits

cr0x@server:~$ grep -E 'CommitLimit|Committed_AS' /proc/meminfo
CommitLimit:    71303168 kB
Committed_AS:   70598240 kB

Sens : Vous êtes proche du plafond de commit. Le noyau peut refuser de nouvelles réservations de mémoire même s’il y a du cache récupérable.

Décision : Réduisez les max mémoire des VM, ajoutez du swap (augmente CommitLimit), ou déplacez des charges.
Les changements de cible balloon n’aideront pas si le max est le problème.

Task 3: Confirm overcommit policy

cr0x@server:~$ sysctl vm.overcommit_memory vm.overcommit_ratio
vm.overcommit_memory = 0
vm.overcommit_ratio = 50

Sens : Le mode 0 est un overcommit heuristique. Le ratio importe surtout pour le mode 2. Néanmoins, le comportement de commit est en jeu.

Décision : Ne changez pas ces réglages en panique à moins de comprendre l’impact. Si vous atteignez les limites de commit,
corriger le dimensionnement est préférable à « simplement overcommitter davantage ».

Task 4: Look for OOM killer evidence on the host

cr0x@server:~$ journalctl -k -b | tail -n 30
Dec 26 10:14:03 pve1 kernel: Out of memory: Killed process 21433 (qemu-system-x86) total-vm:28751400kB, anon-rss:23110248kB, file-rss:0kB, shmem-rss:0kB
Dec 26 10:14:03 pve1 kernel: oom_reaper: reaped process 21433 (qemu-system-x86), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB

Sens : L’hôte a tué QEMU. Cette VM n’a pas « crashé », elle a été exécutée.

Décision : Traitez cela comme une exhaustion mémoire/overcommit de l’hôte. Réduisez la consolidation, limitez l’ARC, ajoutez du swap,
et ne comptez plus sur le ballooning comme ceinture de sécurité.

Task 5: Check memory pressure and reclaim behavior live

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
 6  1 1677720 312000  8200 5120000  40  120   180   260  900 1800 18 12 55 15  0
 5  2 1677800 280000  8100 5010000  10  200   140   320  920 1700 15 10 50 25  0
 7  3 1677850 260000  8000 4920000  80  500   220   600 1100 2200 20 15 35 30  0

Sens : Des si/so non nuls indiquent du swapping. Un wa élevé suggère de l’IO wait.
Si b augmente et id s’effondre, l’hôte est en thrash.

Décision : Si le swapping est soutenu et l’IO wait monte en flèche, arrêtez des VM ou migrez la charge. Vous ne pouvez pas « tuner »
votre sortie d’une tempête de thrash en temps réel.

Task 6: Find the biggest memory consumers on the host (RSS, not VIRT fantasies)

cr0x@server:~$ ps -eo pid,comm,rss,vsz --sort=-rss | head -n 10
 21433 qemu-system-x86 23110248 28751400
 19877 qemu-system-x86 16188012 21045740
  1652 pveproxy          312400  824000
  1321 pvedaemon         210880  693000
  1799 zfs               180200  0
  1544 pvestatd          122000  610000

Sens : RSS est la mémoire résidente réelle. Les processus QEMU dominent, comme prévu.

Décision : Si une VM est hors de contrôle, limitez son max mémoire ou investiguez à l’intérieur du guest.
Si ce sont « plusieurs VM moyennes », c’est de la mathématique de consolidation, pas un seul coupable.

Task 7: Inspect a VM’s memory configuration (ballooning vs max)

cr0x@server:~$ qm config 104 | egrep 'memory|balloon|numa|hugepages'
memory: 32768
balloon: 8192
numa: 1
hugepages: 2

Sens : Max est 32 GiB, cible balloon 8 GiB. Les hugepages sont activées (2 = hugepages de 2 Mo).

Décision : Si le nœud échoue aux allocations, le max de 32 GiB de cette VM peut être trop généreux.
Si les hugepages sont activées, confirmez leur disponibilité (Task 8) ou désactivez-les pour plus de flexibilité.

Task 8: Validate hugepages availability (classic cause of start failures)

cr0x@server:~$ grep -i huge /proc/meminfo
AnonHugePages:   1048576 kB
HugePages_Total:    8192
HugePages_Free:      120
HugePages_Rsvd:       50
Hugepagesize:       2048 kB

Sens : Seulement 120 hugepages libres (~240 MiB). Si vous tentez de démarrer une VM nécessitant beaucoup de hugepages, cela échoue.

Décision : Soit provisionnez suffisamment de hugepages au démarrage, soit cessez d’utiliser les hugepages pour cette classe de VM.
Les hugepages sont un outil de performance, pas une valeur par défaut.

Task 9: Check for THP behavior (can cause latency during pressure)

cr0x@server:~$ cat /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never

Sens : THP est activé en permanence.

Décision : Pour des nœuds sensibles à la latence, envisagez madvise ou never.
Ne changez pas cela en plein incident sauf si vous êtes confiant ; planifiez-le en fenêtre de maintenance et mesurez.

Task 10: If using ZFS, check ARC size quickly

cr0x@server:~$ awk '/^size/ {print}' /proc/spl/kstat/zfs/arcstats
size                            4    34359738368

Sens : L’ARC est ~32 GiB. Sur un hôte de 64 GiB avec beaucoup de VM, cela peut être trop.

Décision : Si vous manquez de mémoire et que l’ARC est large, limitez l’ARC de façon persistante (voir section checklist)
et planifiez un redémarrage si nécessaire pour un soulagement immédiat.

Task 11: Confirm KSM status (helps with many similar VMs, can cost CPU)

cr0x@server:~$ systemctl is-active ksmtuned
inactive

Sens : Le service de tuning KSM n’est pas actif. Sur certains déploiements Proxmox, KSM est configuré différemment ;
ceci est juste un signal rapide.

Décision : Si vous exécutez des dizaines de VM Linux similaires, activer KSM peut réduire l’utilisation mémoire. Si le CPU est
déjà chaud, KSM peut se retourner contre vous. Testez sur un nœud d’abord.

Task 12: Check Proxmox node memory info (what Proxmox thinks is happening)

cr0x@server:~$ pvesh get /nodes/pve1/status | egrep '"memory"|"swap"|"loadavg"'
"loadavg": [
  "2.61",
  "2.45",
  "2.31"
],
"memory": {
  "free": 1288490188,
  "total": 66571993088,
  "used": 651834
},
"swap": {
  "free": 6871947673,
  "total": 8589934592,
  "used": 1717986919
}

Sens : L’API de Proxmox fournit une vue qui peut différer de vos attentes immédiates (unités,
cache et timing). Ne la traitez pas comme une vérité absolue ; recoupez avec free et meminfo.

Décision : Utilisez-la pour l’automatisation et les tableaux de bord, mais lors du débogage d’échecs d’allocation, faites confiance
d’abord aux preuves du noyau et aux journaux QEMU.

Task 13: Inspect a VM start failure in task logs

cr0x@server:~$ journalctl -u pvedaemon -b | tail -n 20
Dec 26 10:18:11 pve1 pvedaemon[1321]: start VM 104: UPID:pve1:0000A3F9:00B2B6D1:676D5A13:qmstart:104:root@pam:
Dec 26 10:18:12 pve1 pvedaemon[1321]: VM 104 qmp command failed - unable to execute QMP command 'cont': Cannot allocate memory
Dec 26 10:18:12 pve1 pvedaemon[1321]: start failed: command '/usr/bin/kvm -id 104 ...' failed: exit code 1

Sens : L’échec se situe au stade de démarrage/cont de QEMU, pas à l’intérieur du guest.

Décision : Concentrez-vous sur les limites de commit de l’hôte, les hugepages, et la fragmentation — pas sur le tuning du guest.

Task 14: Validate container (LXC) memory configuration and swap limit

cr0x@server:~$ pct config 210 | egrep 'memory|swap|features'
memory: 4096
swap: 512
features: nesting=1,keyctl=1

Sens : Le conteneur a 4 GiB de RAM et 512 MiB de swap autorisé. S’il dépasse, les allocations échouent à l’intérieur du conteneur.

Décision : Pour les conteneurs, « impossible d’allouer la mémoire » est souvent une limite de cgroup. Augmentez memory/swap
ou corrigez le comportement de l’application. La mémoire libre de l’hôte ne sauvera pas un LXC avec un plafond dur.

Task 15: Check fragmentation risk signals (quick and dirty)

cr0x@server:~$ cat /proc/buddyinfo | head
Node 0, zone      DMA      1      1      1      1      0      0      0      0      0      0      0
Node 0, zone    DMA32   1024    512    220     12      0      0      0      0      0      0      0
Node 0, zone   Normal   2048   1880    940    110      2      0      0      0      0      0      0

Sens : L’allocateur buddy montre combien de blocs libres existent à différents ordres. Si les ordres supérieurs sont
majoritairement à zéro, les grosses allocations contiguës (y compris certains besoins de hugepage) peuvent échouer même s’il y a « assez » de libre au total.

Décision : Si les hugepages/THP et la compaction sont dans votre configuration, envisagez de réduire la dépendance aux allocations contiguës
ou de planifier des redémarrages de maintenance périodiques pour les nœuds devant satisfaire ces allocations.

Trois mini-récits d’entreprise issus du terrain

Incident : une mauvaise hypothèse (« ballooning signifie qu’il ne réserve pas le max »)

Une entreprise de taille moyenne exploitait un cluster Proxmox interne pour des applications métiers et quelques gros jobs batch.
L’équipe avait pour habitude : mettre la mémoire max des VM élevée « pour que personne n’ait à ouvrir un ticket », puis fixer une cible balloon basse
pour « garder l’utilisation efficace ».

Ça marchait — jusqu’à ce qu’ils mettent à jour quelques VM et lancent un run de reporting trimestriel. De nouveaux processus ont été lancés, des maps mémoire
se sont étendues, et plusieurs VM ont été redémarrées pour des patchs. Soudain : impossible d’allouer la mémoire au démarrage des VM.
Le tableau de bord montrait encore de la « mémoire libre » parce que le cache semblait récupérable.

La cause racine n’était pas une fuite. C’était de la comptabilité. Le Committed_AS de l’hôte avait approché le CommitLimit.
Chaque VM avec un max généreux contribuait au total de mémoire promise, même si elle « restait » habituellement faible. Quand plusieurs redémarrages
ont eu lieu en même temps, QEMU a essayé de réserver ce qu’on lui avait dit qu’il pourrait avoir besoin. Le noyau a refusé. L’erreur était exacte ; leur modèle mental non.

La correction fut ennuyeuse : ils ont réduit le max mémoire des VM à ce que chaque service pouvait justifier, ont gardé le ballooning pour l’élasticité,
et ont ajouté du swap sur les hôtes où il manquait. Surtout, ils ont arrêté de traiter le « max » comme un souhait.
Le run du trimestre suivant a encore provoqué des pics, mais n’a plus cassé les redémarrages.

Optimisation qui s’est retournée contre eux (hugepages partout)

Une autre organisation recherchait la latence. Un ingénieur orienté perf a activé les hugepages pour toute une classe de VM car un blog disait que ça améliorait le comportement du TLB.
Et ça peut le faire. Ils ont aussi laissé les Transparent Huge Pages sur « always », parce que plus de hugepages semblait synonyme de plus de performance.
Voilà comment l’optimisme devient configuration.

Pendant des semaines, tout avait l’air normal. Puis un nœud a commencé à échouer aux démarrages de VM après des migrations routinières. Même VM démarrait sur d’autres nœuds.
Sur ce nœud : impossible d’allouer la mémoire. La mémoire libre n’était pas catastrophique, mais les hugepages libres étaient proches de zéro.
Buddyinfo montrait de la fragmentation : la mémoire était là, juste pas dans les bons morceaux.

Ils ont essayé de « réparer » en augmentant dynamiquement les hugepages. Ça a empiré : le noyau a dû compacter la mémoire pour satisfaire la demande, provoquant des
pics CPU et bloquant le reclaim. La latence a grimpé pendant les heures de pointe. Le rapport d’incident a appelé ça « intermittent ». C’était intermittent comme la gravité l’est quand vous êtes à l’intérieur d’un bâtiment.

Le plan de reprise : désactiver les hugepages pour les VM générales, réserver des hugepages seulement pour un petit ensemble d’instances critiques avec des tailles prévisibles,
et régler THP sur madvise. Les performances ont globalement amélioré parce que le système a arrêté de se battre lui-même.

Pratique ennuyeuse mais correcte qui a sauvé la journée (réserve hôte et caps)

Une troisième équipe exploitait Proxmox pour des charges mixtes : applis web, quelques VM Windows, et quelques appliances gourmandes en stockage.
Ils avaient une règle ennuyeuse : chaque nœud garde une « réserve hôte » fixe de RAM qui n’est jamais allouée aux guests sur le papier.
Ils limitaient aussi l’ARC ZFS dès le premier jour.

Ce n’était pas sophistiqué. Cela signifiait qu’ils pouvaient faire tourner moins de VM par nœud que ce que le tableur exigeait. Mais lors d’un incident où un guest bruyant a soudainement commencé
à consommer de la mémoire (un service Java mal configuré), l’hôte avait suffisamment de marge pour garder les processus QEMU vivants et éviter l’OOM de l’hôte.

Le guest a quand même souffert (comme il se doit), mais le rayon d’explosion est resté confiné à cette VM. Le cluster n’a pas commencé à tuer des charges non liées.
Ils ont drainé le nœud, corrigé la config du guest, et ont repris. Pas de redémarrage à minuit, pas d’échecs en cascade, pas de « pourquoi notre VM pare-feu est morte ? »

La pratique qui les a sauvés n’était pas un réglage noyau secret. C’était de la budgétisation et le refus d’épuiser le fonds d’urgence.

Erreurs fréquentes : symptôme → cause racine → correctif

La VM ne démarre pas : « Impossible d’allouer la mémoire » immédiatement

  • Symptôme : Échec instantané au démarrage ; QEMU sort avec une erreur d’allocation.
  • Cause racine : Limite de commit de l’hôte atteinte, hugepages manquantes, ou fragmentation mémoire pour l’allocation demandée.
  • Correctif : Baissez le max mémoire de la VM ; ajoutez du swap sur l’hôte ; désactivez les hugepages pour cette classe de VM ; provisionnez les hugepages au démarrage si nécessaire.

La VM démarre, puis s’arrête ou se réinitialise de façon aléatoire

  • Symptôme : La VM semble « crasher », les journaux ne montrent pas d’arrêt propre.
  • Cause racine : Le tueur OOM de l’hôte a tué QEMU, souvent après un pic mémoire ou un reclaim intense.
  • Correctif : Trouvez les logs OOM ; réduisez le surcommit de l’hôte ; réservez de la mémoire hôte ; limitez l’ARC ZFS ; assurez-vous que le swap existe et surveillez l’activité de swap.

Les guests ralentissent, puis l’hôte ralentit, puis tout devient philosophique

  • Symptôme : IO wait augmente ; taux swap-in/out montent ; latence VM en pic.
  • Cause racine : Thrashing : pas assez de RAM pour les working sets, et le swap/reclaim dominent.
  • Correctif : Arrêtez ou migrez des VM ; réduisez les limites mémoire ; ajoutez de la RAM ; redesign de consolidation. Aucun sysctl ne vous sauvera ici.

Ballooning activé mais la mémoire ne « revient » jamais

  • Symptôme : L’hôte reste plein ; les guests ne libèrent pas la mémoire comme prévu.
  • Cause racine : Pilote balloon non installé/en cours d’exécution, le guest ne peut pas récupérer, ou le « max » force toujours l’engagement de l’hôte.
  • Correctif : Installez le pilote virtio-balloon ; vérifiez dans le guest ; fixez des max réalistes ; utilisez le ballooning pour l’élasticité, pas comme substitut au dimensionnement.

Tout allait bien jusqu’à ce que les snapshots et la réplication ZFS augmentent

  • Symptôme : La pression mémoire de l’hôte augmente pendant une activité de stockage intensive ; les démarrages de VM échouent.
  • Cause racine : Croissance de l’ARC, pression sur les métadonnées, croissance du slab, et utilisation mémoire pilotée par l’IO.
  • Correctif : Limitez l’ARC ; surveillez le slab ; gardez une marge ; évitez d’exploiter le nœud à 95% « utilisé » en l’appelant efficace.

Les conteneurs affichent « impossible d’allouer la mémoire » alors que l’hôte a l’air bien

  • Symptôme : Les applications LXC échouent les allocations ; l’hôte a l’air OK.
  • Cause racine : La limite de mémoire du cgroup est atteinte (plafond memory/swap du conteneur).
  • Correctif : Augmentez les limites du conteneur ; ajustez l’application ; assurez-vous que le swap du conteneur est autorisé si vous attendez des pointes.

Listes de vérification / plan étape par étape

Étape par étape : réparer un nœud qui lance des erreurs d’allocation

  1. Confirmer OOM hôte vs échec au démarrage.
    Vérifiez journalctl -k pour les kills OOM et les journaux pvedaemon pour le contexte d’échec de démarrage.
  2. Mesurer la pression de commit.
    Si Committed_AS est proche de CommitLimit, vous êtes en territoire « promesses > réalité ».
  3. Lister les VM avec des max mémoire élevés.
    Réduisez le max mémoire des coupables. Ne vous contentez pas d’ajuster les cibles balloon.
  4. Vérifier les hugepages et les réglages THP.
    Si les hugepages sont activées pour des VM, assurez-vous d’une préallocation adéquate ou désactivez-les pour les charges générales.
  5. Vérifier l’ARC ZFS si applicable.
    Si l’ARC est volumineuse et que vous êtes avant tout un hôte VM, limitez-la.
  6. S’assurer que le swap existe et est sain.
    Ajoutez du swap si nécessaire ; surveillez si/so. Le swap est pour les pics, pas pour payer le loyer.
  7. Réserver de la mémoire pour l’hôte.
    Gardez une réserve fixe pour l’hôte + ZFS + overhead QEMU. Votre futur vous remerciera en silence.
  8. Re-tester les démarrages de VM dans une séquence contrôlée.
    Ne relancez pas tout d’un coup après le tuning. Démarrez d’abord les services critiques.

Réglage persistant : cap de l’ARC ZFS (exemple)

Si le nœud est un hôte VM et que ZFS est un moyen, fixez un maximum d’ARC. Une méthode courante :
créez un fichier de configuration modprobe et mettez à jour l’initramfs pour qu’il s’applique au démarrage.

cr0x@server:~$ echo "options zfs zfs_arc_max=17179869184" | sudo tee /etc/modprobe.d/zfs.conf
options zfs zfs_arc_max=17179869184
cr0x@server:~$ sudo update-initramfs -u
update-initramfs: Generating /boot/initrd.img-6.8.12-4-pve

Sens : ARC limité à 16 GiB (valeur en octets). Vous venez de dire à ZFS qu’il ne peut pas manger toute la machine.

Décision : Choisissez un cap qui laisse assez de RAM pour les guests plus la réserve hôte. Validez après redémarrage en relisant arcstats.

Réglage persistant : ajouter du swap hôte (exemple fichier)

cr0x@server:~$ sudo fallocate -l 8G /swapfile
cr0x@server:~$ sudo chmod 600 /swapfile
cr0x@server:~$ sudo mkswap /swapfile
Setting up swapspace version 1, size = 8 GiB (8589930496 bytes)
no label, UUID=0a3b1e4c-2f1e-4f65-a3da-b8c6e3f3a8d7
cr0x@server:~$ sudo swapon /swapfile
cr0x@server:~$ swapon --show
NAME      TYPE SIZE USED PRIO
/swapfile file   8G   0B   -2

Sens : Le swap est actif. CommitLimit augmente, et vous avez un tampon contre des pics d’allocations soudains.

Décision : Si l’utilisation du swap devient soutenue avec un si/so élevé, ce n’est pas « fonctionner comme prévu ».
C’est un signe pour réduire la consolidation ou ajouter de la RAM.

Politique : réserver de la RAM pour l’hôte (règle simple qui fonctionne)

  • Réservez au moins 10–20% de la RAM hôte pour l’hôte sur des nœuds mixtes.
    Plus si vous exécutez ZFS, Ceph, du réseau lourd, ou beaucoup de petites VM.
  • Gardez un objectif « somme des max des guests » que vous pouvez défendre. Si la somme des max VM dépasse un multiple défini de la RAM hôte,
    ne le faites qu’intentionnellement et seulement si le comportement des charges le permet.

Checklist ballooning (utilisez-le correctement)

  • Activez le ballooning uniquement si le guest supporte virtio-balloon.
  • Fixez le max mémoire proche de la réalité ; la cible balloon peut être plus basse pour l’inactivité.
  • Surveillez le swap du guest et les événements OOM dans le guest après activation du ballooning.
  • Ne balloonnez pas les bases de données à moins d’accepter des pics IO et une latence imprévisible.
  • FAQ

    1) Pourquoi Proxmox dit « impossible d’allouer la mémoire » alors que free montre des Go libres ?

    Parce que free montre un instantané de la mémoire physique, tandis que la comptabilité de commit du noyau et les règles de fragmentation
    peuvent refuser une nouvelle allocation. De plus, « libre » ignore si la mémoire est disponible sous la forme demandée (par ex. hugepages).

    2) Le ballooning réduit-il ce que l’hôte doit réserver ?

    Il réduit ce que le guest utilise à l’exécution, mais si votre VM a un max élevé, l’hôte peut rester engagé par la promesse.
    Le ballooning n’est pas une carte « pas besoin de dimensionner ».

    3) Dois-je mettre vm.overcommit_memory=1 pour arrêter les erreurs d’allocation ?

    C’est un instrument brutal. Cela peut réduire les échecs au démarrage, mais augmente le risque d’OOM catastrophique plus tard.
    En production, préférez corriger le dimensionnement des VM et ajouter du swap plutôt que d’assouplir les garde-fous du noyau.

    4) Combien de swap un hôte Proxmox devrait-il avoir ?

    Assez pour survivre aux pics et améliorer le CommitLimit, pas assez pour masquer un surcommit chronique. Communément : quelques Go à
    quelques dizaines de Go selon la RAM de l’hôte et la volatilité des charges. Mesurez l’activité de swap ; si elle est constamment élevée, vous êtes sous-dimensionné.

    5) L’ARC ZFS est-il la raison pour laquelle mon nœud « manque de mémoire » ?

    Parfois. L’ARC peut grossir et concurrencer les VM. Si les démarrages de VM échouent ou que l’hôte OOM alors que l’ARC est massif,
    limitez l’ARC. Si l’ARC est modeste, cherchez ailleurs (limites de commit, hugepages, VM runaway).

    6) Dois-je activer KSM sur Proxmox ?

    Si vous exécutez beaucoup de VM similaires (même OS, pages mémoire similaires), KSM peut économiser de la RAM. Cela coûte du CPU et peut ajouter de la latence.
    Activez-le délibérément, mesurez l’overhead CPU, et ne le traitez pas comme de la mémoire gratuite.

    7) Pourquoi les conteneurs atteignent « impossible d’allouer la mémoire » alors que l’hôte va bien ?

    LXC est gouverné par les cgroups. Un conteneur peut manquer de mémoire à l’intérieur de sa limite même si l’hôte a de la marge.
    Ajustez les limites pct memory/swap ou corrigez la charge du conteneur.

    8) Les hugepages valent-elles le coup ?

    Pour certaines charges à haut débit et sensibles à la latence : oui. Pour la consolidation générale : souvent non.
    Les hugepages augmentent la prévisibilité du comportement du TLB mais réduisent la flexibilité et peuvent créer des échecs de démarrage si mal provisionnées.

    9) Quelle est la différence entre OOM guest et OOM hôte ?

    L’OOM guest se produit à l’intérieur de la VM : le noyau du guest tue des processus, mais la VM reste en ligne. L’OOM hôte tue des processus sur
    l’hyperviseur, y compris QEMU — votre VM disparaît. L’OOM hôte est celui qui vous gâche l’après-midi.

    10) Puis-je « réparer » ça définitivement sans ajouter de RAM ?

    Souvent oui : fixez des max mémoire réalistes pour les VM, réservez de la RAM pour l’hôte, limitez l’ARC si besoin, et évitez des ratios de surcommit qui supposent des miracles.
    Si les working sets dépassent réellement la RAM physique, la solution permanente est : plus de RAM ou moins de charges par nœud.

    Étapes suivantes (la voie sensée)

    « Impossible d’allouer la mémoire » dans Proxmox n’est pas une malédiction. C’est le noyau qui applique une frontière que vous avez déjà franchie en
    politique, configuration ou attentes.

    1. Cessez de traiter le max mémoire des VM comme une suggestion. Faites-en un contrat.
    2. Utilisez le ballooning pour l’élasticité, pas pour le déni. Ciblez bas, plafonnez de façon réaliste.
    3. Donnez à l’hôte un fonds d’urgence. Réservez de la RAM ; ajoutez du swap ; maintenez l’ARC ZFS dans sa voie.
    4. Privilégiez des nœuds prévisibles plutôt que des réglages héroïques. Séparez les charges quand leurs modes d’échec diffèrent.
    5. Opérationnalisez cela. Ajoutez des alertes pour la proximité de CommitLimit, le taux swap-in/out, les logs OOM, et la taille de l’ARC.

    Faites cela, et la prochaine fois que Proxmox se plaindra de mémoire, ce sera parce que vous êtes vraiment à court — pas parce que votre
    configuration a raconté une histoire charmante que le noyau a refusé de croire.

    PostgreSQL vs SQLite sur un VPS : le choix le plus rapide sans regrets

    Vous êtes sur un VPS. Vous voulez « une base de données ». Pas un projet du week-end, pas une ferme à yaks. Quelque chose qui ne vous réveillera pas à 03:00 parce qu’un seul fichier est coincé, ou parce que votre appli reçoit soudainement du vrai trafic et que votre choix « simple » se transforme en une migration douloureuse.

    La façon la plus rapide de choisir entre PostgreSQL et SQLite est d’arrêter de débattre des fonctionnalités et de poser une seule question brutale : où se situe votre frontière de concurrence et de défaillance ? Si elle est à l’intérieur d’un seul processus, SQLite est un scalpel. Si elle s’étend sur de nombreux processus, utilisateurs, jobs et connexions, PostgreSQL est votre clé robuste et éprouvée.

    La décision en une minute

    Si vous ne lisez que cette section, vous ferez quand même un choix respectable.

    Choisissez SQLite si tout cela est vrai

    • Votre appli est principalement à un seul rédacteur et trafic modeste (pensez : un processus web ou un worker de file qui écrit, pas une nuée).
    • Vous pouvez vivre avec la sémantique de verrouillage basée sur le fichier et l’occasionnel « base de données verrouillée » si vous en faites un mauvais usage.
    • Vous voulez zéro overhead d’exploitation : pas de démon, pas de vacuum en arrière-plan à tuner, pas de drame de connection pooling.
    • Votre domaine de défaillance est « ce VPS et ce disque » et cela vous va.
    • Vous voulez une parité dev locale facile : livrer un seul fichier DB est un atout.

    Choisissez PostgreSQL si l’un de ces points est vrai

    • Vous avez plusieurs rédacteurs, plusieurs instances d’appli, cron jobs, workers, requêtes analytiques, outils d’administration… tout ce qui se comporte comme une petite foule.
    • Vous avez besoin d’une forte concurrence sans transformer votre appli en coordinateur de verrous.
    • Vous tenez à l’isolation, la durabilité et la récupérabilité face à des modes de défaillance réels et désordonnés.
    • Vous voulez des migrations en ligne, des index plus riches et des plans de requête qui vont au-delà du « mignon ».
    • Vous prévoyez une croissance et préférez scaler en ajoutant CPU/RAM maintenant et des réplicas plus tard, plutôt que faire une migration risquée plus tard.

    Règle empirique : si votre base de données doit médier l’impatience humaine (trafic web) et l’impatience machine (jobs), PostgreSQL est l’adulte dans la pièce.

    Blague #1 : SQLite, c’est comme un vélo : rapide, élégant et parfait jusqu’à ce que vous essayiez de déplacer un canapé avec.

    Un modèle mental qui évite les regrets

    La plupart des débats « Postgres vs SQLite » meurent parce que les gens comparent la syntaxe SQL ou des checklists de fonctionnalités. Le vrai choix porte sur la forme opérationnelle : qui parle à la base, à quelle fréquence, et ce qui se passe quand ça casse.

    SQLite : une bibliothèque avec un fichier, pas un serveur

    SQLite s’exécute en processus. Il n’y a pas de démon serveur acceptant des connexions. Votre appli lie une bibliothèque ; la « base de données » est un fichier (plus des fichiers journaux/WAL optionnels). Cela signifie :

    • La latence peut être excellente parce qu’il n’y a pas de saut réseau. Les appels sont des appels de fonction.
    • La concurrence est limitée par le verrouillage de fichier. Les lectures vont bien. Les écritures nécessitent de la coordination ; WAL améliore cela mais ne transforme pas tout en foire d’empoigne.
    • La durabilité dépend des sémantiques du système de fichiers, des options de montage et de votre usage des paramètres synchronous. Ce n’est pas « dangereux », c’est « vous gérez les zones tranchantes ».
    • Les sauvegardes sont des sauvegardes de fichier, ce qui peut être merveilleusement simple—jusqu’à ce que vous en fassiez une au mauvais moment sans utiliser les API de sauvegarde de SQLite.

    PostgreSQL : un serveur avec des processus, de la mémoire et des convictions

    PostgreSQL fonctionne comme un serveur de base de données avec ses propres processus, caches, write-ahead log (WAL), vacuum en arrière-plan et des sémantiques transactionnelles bien définies. Cela signifie :

    • Haute concurrence grâce à MVCC (contrôle de concurrence multi-version) : les lecteurs ne bloquent pas les rédacteurs comme le feraient des verrous de fichiers.
    • La durabilité et la récupération après crash sont au cœur du système. Il faut toujours configurer et tester, mais le système est conçu pour les mauvais jours.
    • Il y a un overhead opérationnel : mises à jour, sauvegardes, monitoring, vacuum et gestion des connexions.
    • Les voies d’évolution sont plus claires : réplication, réplicas de lecture, partitionnement, poolers de connexion et un écosystème mature.

    La question de la frontière

    Demandez : « La base de données est-elle une frontière de service partagée ? » Si oui, PostgreSQL. Si non, SQLite peut être une base légitime en production. Ne sous-estimez pas la fréquence à laquelle un « non » devient un « oui » quand vous ajoutez un worker, puis une seconde instance d’appli, puis un tableau d’administration qui exécute des requêtes lourdes.

    Faits intéressants et un peu d’histoire

    Un peu de contexte aide, car les choix de conception n’étaient pas arbitraires. Ce sont des cicatrices d’usage réel.

    1. SQLite est né en 2000 comme base embarquée pour éviter l’overhead des bases client/serveur pour un projet logiciel précis ; il est devenu le moteur « petit SQL » par défaut dans le monde.
    2. PostgreSQL remonte aux années 1980 (projet POSTGRES à UC Berkeley), et son ADN se voit : extensibilité, exactitude et obsession académique pour le comportement transactionnel.
    3. SQLite est probablement le moteur de base de données le plus déployé car il est embarqué dans les téléphones, navigateurs, systèmes d’exploitation et d’innombrables applications comme bibliothèque.
    4. PostgreSQL a popularisé l’extensibilité riche via types personnalisés, opérateurs et extensions ; c’est pourquoi il est la plateforme « SQL plus » par défaut dans beaucoup de stacks modernes.
    5. Le mode WAL de SQLite a été ajouté plus tard pour réduire le blocage des rédacteurs et améliorer la concurrence ; cela a changé ce pour quoi SQLite est adapté en production.
    6. L’MVCC de PostgreSQL signifie que d’anciennes versions de lignes traînent jusqu’à ce que vacuum les nettoie ; c’est une fonctionnalité de performance et une corvée opérationnelle.
    7. SQLite est célèbre pour la portabilité stricte de son fichier de base entre architectures et versions, mais il dépend toujours du comportement du système de fichiers pour la durabilité.
    8. Le WAL de PostgreSQL s’appelle aussi WAL (même acronyme, implémentation différente), et il est la base pour la réplication et la récupération PITR.
    9. « database is locked » dans SQLite n’est pas un bug ; c’est le résultat explicite du modèle de verrouillage. Le bug est votre hypothèse qu’il se comporte comme une base serveur.

    Réalités du VPS : disques, mémoire et voisins

    Un VPS n’est pas un laptop et pas une base gérée. C’est une petite tranche d’une machine plus grande avec IO partagé et parfois des voisins imprévisibles. Votre choix de base doit en tenir compte.

    Le benchmark IO est le premier mensonge qu’on vous raconte

    Sur un VPS, votre « SSD » peut être rapide, ou il peut être « rapide quand les voisins dorment ». SQLite et PostgreSQL tiennent compte du comportement de fsync, mais ils en font l’expérience différemment :

    • SQLite écrit dans un seul fichier de base (plus journaux/WAL). Les écritures aléatoires peuvent être pénalisantes si votre charge génère beaucoup d’écritures.
    • PostgreSQL écrit dans plusieurs fichiers : fichiers de données et segments WAL. Les écritures WAL sont plus séquentielles et peuvent être plus indulgentes pour des disques réels, mais vous avez alors des processus en arrière-plan et des checkpoints.

    La mémoire n’est pas que « cache » ; c’est une politique

    SQLite s’appuie fortement sur le cache de pages de l’OS. C’est acceptable—Linux est bon pour le caching. PostgreSQL a ses propres shared buffers en plus du cache OS. Si vous le dimensionnez mal sur un petit VPS, vous pouvez vous retrouver avec un double-cache et priver le reste du système.

    Le modèle de processus compte quand la RAM est faible

    SQLite vit dans votre processus applicatif. PostgreSQL utilise plusieurs processus et de la mémoire par connexion. Sur un VPS de 1 Go, une pile de connexions inactives peut être un bug de performance, pas un détail mineur. Si vous exécutez Postgres sur du petit fer, vous apprendrez à aimer le pooling de connexions.

    Rayon d’explosion opérationnel

    Le rayon d’explosion de SQLite est souvent « ce fichier ». Celui de PostgreSQL est « ce cluster », mais avec de meilleurs outils pour isoler et récupérer. SQLite peut être récupéré en copiant un fichier—sauf si vous le copiez au mauvais moment. PostgreSQL peut être récupéré en rejouant le WAL—sauf si vous n’avez jamais testé vos sauvegardes. Choisissez votre poison ; puis atténuez-le.

    Tâches pratiques : commandes, sorties, décisions (12+)

    Voici des tâches que vous pouvez lancer sur un VPS aujourd’hui. Chacune donne un signal, pas une impression. Le but est de décider sur la base de preuves : capacité IO, besoins de concurrence et risques de défaillance.

    Task 1: Check CPU and memory pressure (are you even allowed to run Postgres?)

    cr0x@server:~$ lscpu | egrep 'Model name|CPU\(s\)'
    CPU(s):                               2
    Model name:                           Intel(R) Xeon(R) CPU E5-2690 v4 @ 2.60GHz
    
    cr0x@server:~$ free -h
                   total        used        free      shared  buff/cache   available
    Mem:           1.0Gi       220Mi       180Mi        12Mi       620Mi       690Mi
    Swap:          1.0Gi         0B       1.0Gi
    

    Ce que cela signifie : Sur 1 Go de RAM, Postgres est possible mais il faut de la discipline (pooler de connexions, tuning mémoire). SQLite paraîtra sans effort.

    Décision : Si vous ne pouvez pas allouer quelques centaines de Mo pour Postgres plus de la marge pour votre appli, préférez SQLite ou augmentez le VPS.

    Task 2: Identify your storage type and mount options (durability lives here)

    cr0x@server:~$ findmnt -no SOURCE,FSTYPE,OPTIONS /
    /dev/vda1 ext4 rw,relatime,errors=remount-ro
    

    Ce que cela signifie : ext4 avec relatime est normal. Si vous voyez des options étranges comme data=writeback ou un FS réseau exotique, vous devez traiter les affirmations de durabilité de SQLite avec suspicion et tuner PostgreSQL aussi.

    Décision : Si vous êtes sur un stockage réseau ou bizarre, Postgres avec WAL+fsync testé est généralement plus sûr que « copier le fichier de base ».

    Task 3: Quick disk latency check (your future “db is slow” ticket)

    cr0x@server:~$ iostat -xz 1 3
    Linux 6.2.0 (server) 	12/30/2025 	_x86_64_	(2 CPU)
    
    avg-cpu:  %user   %nice %system %iowait  %steal   %idle
               3.10    0.00    1.20    0.40    0.10   95.20
    
    Device            r/s     w/s   rkB/s   wkB/s  await  svctm  %util
    vda              5.00    8.00   80.0   210.0   2.10   0.40   0.52
    

    Ce que cela signifie : await en chiffres faibles est correct. Si vous voyez des pics de 20–100ms, SQLite et Postgres souffriront, mais SQLite le manifestera comme des blocages à l’intérieur des threads appli.

    Décision : Un IO wait élevé plaide pour Postgres avec tuning de checkpoint et possiblement migrer vers un stockage meilleur ; cela plaide aussi pour réduire l’amplification d’écritures de toute façon.

    Task 4: Measure filesystem sync cost (SQLite and Postgres both pay this bill)

    cr0x@server:~$ sudo dd if=/dev/zero of=/var/tmp/fsync.test bs=4k count=25000 conv=fdatasync status=progress
    102400000 bytes (102 MB, 98 MiB) copied, 1.52 s, 67.4 MB/s
    25000+0 records in
    25000+0 records out
    102400000 bytes (102 MB, 98 MiB) copied, 1.52 s, 67.3 MB/s
    

    Ce que cela signifie : C’est grossier, mais ça approximative « à quel point forcer la durabilité est coûteux ». Si c’est glacial, vos réglages « sûrs » feront mal.

    Décision : Si le fsync forcé est cher, SQLite a besoin de WAL + paramètres synchronous sensés ; Postgres a besoin de checkpoints ajustés et de ne pas abuser de synchronous_commit pour des écritures non critiques.

    Task 5: Verify open file limits (Postgres will care more)

    cr0x@server:~$ ulimit -n
    1024
    

    Ce que cela signifie : 1024 est serré pour Postgres sous charge avec beaucoup de connexions et de fichiers. SQLite s’en soucie moins, mais votre appli peut en souffrir.

    Décision : Si vous choisissez Postgres, augmentez les limites via systemd ou limits.conf ; si vous ne pouvez pas, gardez les connexions basses et utilisez un pooler.

    Task 6: Inspect live connection count (if it’s already a crowd, SQLite will get spicy)

    cr0x@server:~$ sudo ss -tanp | awk '$4 ~ /:5432$/ {c++} END {print c+0}'
    0
    

    Ce que cela signifie : Pas de Postgres pour l’instant, mais le motif compte : combien de clients DB concurrents existeront ?

    Décision : Si vous attendez des dizaines/centaines de connexions concurrentes, Postgres plus un pooler gagne. SQLite n’a pas de « connexions » au même sens ; il y a des « threads et processus se battant pour un fichier ».

    Task 7: Create a SQLite database with WAL and inspect pragmas (make it less fragile)

    cr0x@server:~$ sqlite3 /var/lib/myapp/app.db 'PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL; PRAGMA wal_autocheckpoint=1000;'
    wal
    

    Ce que cela signifie : Mode WAL activé ; synchronous NORMAL est un compromis courant (suffisamment durable pour beaucoup d’applis, moins d’IO que FULL).

    Décision : Si vous choisissez SQLite, soyez explicite sur les pragmas. Les réglages par défaut ne sont pas une « politique de production », ce sont des « valeurs génériques de bibliothèque ».

    Task 8: Simulate concurrent SQLite writes (spot the lock wall early)

    cr0x@server:~$ for i in $(seq 1 20); do (sqlite3 /var/lib/myapp/app.db "BEGIN IMMEDIATE; CREATE TABLE IF NOT EXISTS t(x); INSERT INTO t VALUES($i); COMMIT;" >/dev/null 2>&1 &); done; wait; echo done
    done
    

    Ce que cela signifie : C’est un test approximatif. Si vous relancez avec plus de contention et commencez à voir « database is locked » sur stderr, c’est votre sirène d’avertissement.

    Décision : Si votre charge réelle ressemble à ça (beaucoup de rédacteurs), arrêtez de romanticiser SQLite et utilisez Postgres.

    Task 9: Install Postgres and confirm the service health

    cr0x@server:~$ sudo apt-get update -qq
    ...output...
    
    cr0x@server:~$ sudo apt-get install -y postgresql
    ...output...
    
    cr0x@server:~$ sudo systemctl status postgresql --no-pager
    ● postgresql.service - PostgreSQL RDBMS
         Loaded: loaded (/lib/systemd/system/postgresql.service; enabled)
         Active: active (exited)
    

    Ce que cela signifie : Sur Debian/Ubuntu, le service wrapper peut afficher « active (exited) » tandis que les unités de cluster tournent. Ne paniquez pas ; vérifiez le cluster.

    Décision : Si vous ne parvenez pas à garder un service sain sur votre VPS (permissions, disque plein, pression mémoire), SQLite peut être le choix plus sain jusqu’à stabilisation de l’hôte.

    Task 10: Check Postgres cluster readiness

    cr0x@server:~$ pg_lsclusters
    Ver Cluster Port Status Owner    Data directory              Log file
    16  main    5432 online postgres /var/lib/postgresql/16/main /var/log/postgresql/postgresql-16-main.log
    

    Ce que cela signifie : C’est en ligne. Vous avez un répertoire de données et un chemin de log—deux choses que vous apprendrez à respecter.

    Décision : Si Postgres démarre proprement et reste en ligne sous votre appli, c’est un signal fort que vous pouvez assumer l’ops.

    Task 11: Inspect Postgres durability and checkpoint settings (don’t fly blind)

    cr0x@server:~$ sudo -u postgres psql -c "SHOW synchronous_commit; SHOW fsync; SHOW full_page_writes; SHOW checkpoint_timeout; SHOW max_wal_size;"
     synchronous_commit
    -------------------
     on
    (1 row)
    
     fsync
    -------
     on
    (1 row)
    
     full_page_writes
    ------------------
     on
    (1 row)
    
     checkpoint_timeout
    --------------------
     5min
    (1 row)
    
     max_wal_size
    --------------
     1GB
    (1 row)
    

    Ce que cela signifie : Les valeurs par défaut sont conservatrices. Elles visent la sécurité sur du matériel générique, pas nécessairement votre VPS spécifique.

    Décision : Si vous avez besoin d’un débit d’écriture élevé, vous pouvez tuner les checkpoints et la taille WAL. Si vous voulez une sécurité maximale, laissez ces valeurs conservatrices et investissez dans les sauvegardes et tests.

    Task 12: Spot vacuum pressure (Postgres’s “housekeeping tax”)

    cr0x@server:~$ sudo -u postgres psql -c "SELECT relname, n_dead_tup FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 5;"
     relname | n_dead_tup
    ---------+------------
    (0 rows)
    

    Ce que cela signifie : Pas encore de tables utilisateurs. Plus tard, cela montre si des tuples morts s’accumulent. Des tas signifient bloat, requêtes plus lentes et finalement des problèmes de pagination.

    Décision : Si vous choisissez Postgres, surveillez vacuum/bloat. Si vous ne pouvez pas vous engager là-dedans, la simplicité de SQLite devient attractive—à condition que la concurrence convienne.

    Task 13: Identify slow queries in Postgres (decide if you need indexes or a different DB)

    cr0x@server:~$ sudo -u postgres psql -c "SHOW shared_preload_libraries;"
     shared_preload_libraries
    --------------------------
    (1 row)
    

    Ce que cela signifie : Si pg_stat_statements n’est pas activé, il vous manque une lentille clé sur le comportement des requêtes.

    Décision : Si vous exécutez quelque chose au-delà d’un trafic jouet, activez les stats de requêtes et traitez-les comme de la télémétrie de production. SQLite a des options, mais Postgres rend ce type d’analyse routinier.

    Task 14: Estimate DB size and growth (SQLite file vs Postgres cluster)

    cr0x@server:~$ du -sh /var/lib/myapp/app.db
    48M	/var/lib/myapp/app.db
    
    cr0x@server:~$ sudo -u postgres psql -c "SELECT pg_size_pretty(pg_database_size(current_database()));"
     pg_size_pretty
    ----------------
     7289 kB
    (1 row)
    

    Ce que cela signifie : SQLite est un seul fichier ; Postgres est un arbre de répertoires plus WAL. Les schémas de croissance diffèrent : le fichier SQLite grossit et peut ne pas rétrécir ; Postgres peut gonfler sans vacuum.

    Décision : Si vous avez besoin d’une gestion prévisible de la taille et de la rétention, Postgres avec vacuum et partitionnement appropriés (si nécessaire) a tendance à être plus facile à contrôler qu’un fichier monolithique.

    Task 15: Test backup/restore workflow (this decides whether you sleep)

    cr0x@server:~$ sqlite3 /var/lib/myapp/app.db ".backup '/var/backups/app.db.bak'"
    
    cr0x@server:~$ ls -lh /var/backups/app.db.bak
    -rw-r--r-- 1 root root 48M Dec 30 03:12 /var/backups/app.db.bak
    
    cr0x@server:~$ sudo -u postgres pg_dump -Fc -f /var/backups/pg.dump postgres
    
    cr0x@server:~$ ls -lh /var/backups/pg.dump
    -rw-r--r-- 1 postgres postgres 36K Dec 30 03:13 /var/backups/pg.dump
    

    Ce que cela signifie : Les deux peuvent être sauvegardés. L’essentiel est la cohérence et les tests de restauration. SQLite a besoin de la bonne méthode de sauvegarde ; Postgres a besoin que vous pratiquiez la restauration et les permissions.

    Décision : Si vous ne pouvez pas et ne ferez pas de tests de restauration, ne choisissez ni l’un ni l’autre—parce que vous ne choisissez pas une base, vous choisissez un incident futur.

    Feuille de jeu pour diagnostic rapide

    Ceci est la séquence de triage « quelque chose est lent ». L’objectif est d’isoler le goulot d’étranglement en minutes, pas de débattre de l’architecture sur Slack pendant des heures.

    Premier : est-ce CPU, mémoire ou disque ?

    cr0x@server:~$ uptime
     03:20:11 up 12 days,  2:41,  1 user,  load average: 0.22, 0.40, 0.35
    
    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
     1  0      0 184320  28000 635000    0    0    10    25  120  180  3  1 95  1  0
     0  0      0 183900  28000 635200    0    0     0     0  110  170  2  1 97  0  0
    

    Interprétation : Un wa élevé signifie attente IO ; un si/so élevé signifie swap ; un r élevé avec peu d’idle signifie pression CPU.

    Action : Si l’hôte swap, corrigez la mémoire d’abord (réduisez les connexions, tunez Postgres, ajoutez de la RAM). Si l’attente IO est élevée, regardez les checkpoints, le coût de fsync et les patterns d’écriture.

    Second : la base est-elle verrouillée ou bloquée ?

    SQLite : cherchez des erreurs de verrou dans les logs d’appli ; vérifiez si vous faites des transactions longues.

    Postgres : vérifiez les verrous bloquants.

    cr0x@server:~$ sudo -u postgres psql -c "SELECT pid, wait_event_type, wait_event, state, query FROM pg_stat_activity WHERE state <> 'idle' ORDER BY pid;"
     pid  | wait_event_type | wait_event | state  | query
    ------+-----------------+------------+--------+-------
    (0 rows)
    

    Interprétation : Si vous voyez des sessions en attente de verrous, vous n’êtes pas « lent », vous êtes sérialisé. Remède différent : raccourcir les transactions, ajouter des index pour diminuer la durée des verrous, éviter les DDL longue durée pendant les heures de pointe.

    Troisième : problème de requête ou de capacité ?

    Pour Postgres, identifiez les requêtes lentes et faites des EXPLAIN. Pour SQLite, examinez vos patterns d’accès et d’index, et envisagez de sortir les requêtes lourdes du chemin chaud.

    cr0x@server:~$ sudo -u postgres psql -c "EXPLAIN (ANALYZE, BUFFERS) SELECT 1;"
                                          QUERY PLAN
    --------------------------------------------------------------------------------------
     Result  (cost=0.00..0.01 rows=1 width=4) (actual time=0.003..0.004 rows=1 loops=1)
     Planning Time: 0.020 ms
     Execution Time: 0.010 ms
    (3 rows)
    

    Interprétation : En usage réel, cherchez des scans séquentiels sur de grandes tables, des hits massifs de buffers, ou du temps passé en attente IO.

    Action : Si les requêtes sont lentes à cause d’index manquants, corrigez le schéma. Si c’est le disque lent, améliorez le stockage ou réduisez le churn d’écritures. Si c’est la concurrence, corrigez le pooling ou choisissez la bonne base.

    Erreurs courantes (symptômes → cause → correctif)

    Ce ne sont pas des échecs moraux. Ce sont des conséquences prévisibles de traiter une base comme une boîte noire.

    1) “database is locked” apparaît sporadiquement (SQLite)

    Symptômes : Erreurs applicatives sous charge, pics lors de jobs en arrière-plan, requêtes échouant puis réussissant au retry.

    Cause : Plusieurs rédacteurs ou transactions longues tenant des verrous d’écriture. WAL aide, mais un seul rédacteur a toujours besoin de temps.

    Correctif : Activez WAL ; gardez les transactions courtes ; sérialisez les écritures via une file de jobs ; ajoutez busy_timeout ; ou migrez vers Postgres si vous avez besoin d’écritures concurrentes.

    2) SQLite est rapide jusqu’à ce que vous déployiez plusieurs instances

    Symptômes : Fonctionne en dev, instable en prod ; performance qui s’effondre seulement en scalant horizontalement.

    Cause : Le verrouillage de fichier entre processus devient contention. Aussi : les systèmes de fichiers partagés sont un piège.

    Correctif : Ne partagez pas SQLite sur NFS. Si vous avez besoin de plus d’un processus rédacteur, utilisez Postgres.

    3) Postgres est « lent » mais le CPU est inactif

    Symptômes : Latence élevée, CPU bas, pauses périodiques.

    Cause : Attente IO pendant les checkpoints ou charge d’écriture fsync-heavy ; max_wal_size trop petit ; stockage médiocre.

    Correctif : Augmentez max_wal_size ; tunez les checkpoints ; déplacez le WAL sur un disque plus rapide si possible ; réduisez les écritures synchrones pour les chemins non critiques (avec précaution).

    4) Postgres s’écroule avec beaucoup de connexions sur un petit VPS

    Symptômes : Pics mémoire, OOM kills, « too many clients », timeouts aléatoires.

    Cause : Un modèle « une connexion par requête » ; coût mémoire par connexion ; pas de pooling.

    Correctif : Utilisez PgBouncer ; réduisez max_connections ; utilisez une taille de pool sensée ; faites en sorte que l’appli réutilise les connexions.

    5) Des sauvegardes existent mais les restaurations échouent

    Symptômes : Test de restauration échoue ; permissions brisées ; rôles manquants ; fichier de sauvegarde SQLite corrompu ou incohérent.

    Cause : Sauvegardes prises incorrectement (copie de fichier SQLite en cours d’écriture) ou non testées (pg_dump manquant des globals/rôles).

    Correctif : Pour SQLite, utilisez .backup ou l’API de sauvegarde ; pour Postgres, faites des exercices de restauration incluant rôles et schéma ; automatisez la vérification.

    6) Les tables Postgres gonflent et les requêtes se dégradent en semaines

    Symptômes : Utilisation disque qui augmente plus vite que les données ; index qui grossissent ; requêtes lentes ; vacuum constant.

    Cause : Accumulation de tuples morts MVCC ; autovacuum qui n’est pas à la hauteur ; patterns UPDATE/DELETE agressifs.

    Correctif : Tuner autovacuum par table ; éviter les hot updates quand possible ; envisager partitionnement ou maintenance périodique.

    7) Le fichier SQLite gonfle et ne rétrécit jamais

    Symptômes : Utilisation disque qui augmente même après des deletes ; VPS manque d’espace disque.

    Cause : SQLite réutilise les pages mais ne rend pas toujours l’espace au système de fichiers ; fragmentation ; gros deletes.

    Correctif : VACUUM périodique (coûteux) ; concevoir une stratégie de rétention ; envisager de scinder de grandes tables ou migrer vers Postgres si le churn est élevé.

    8) « On a utilisé Postgres parce que c’est entreprise » et maintenant l’ops coule

    Symptômes : Personne ne gère les upgrades, vacuum, sauvegardes ; la DB est un animal de compagnie, pas du bétail.

    Cause : Avoir choisi Postgres sans allouer la maturité opérationnelle.

    Correctif : Soit investissez dans les bases ops (monitoring, drills de restauration, cadence de mise à jour) soit restez simple avec SQLite jusqu’à ce que vous ayez vraiment besoin d’une DB serveur.

    Trois mini-récits d’entreprise

    Mini-récit 1 : L’incident causé par une mauvaise hypothèse (fichier SQLite sur « stockage partagé »)

    L’entreprise était de taille moyenne, le produit se portait bien, et quelqu’un a eu l’idée lumineuse : exécuter deux instances d’appli derrière un load balancer « pour la résilience ». La base était SQLite, posée sur ce que le fournisseur VPS annonçait comme « stockage partagé », monté dans les deux instances. Ça semblait élégant. Un fichier. Deux instances. Que pourrait-il mal se passer ?

    Ça a marché quelques jours. Puis le premier pic de trafic—rien de dramatique, juste un email marketing. Les requêtes ont commencé à s’accumuler. La latence a monté. Certains utilisateurs ont eu des erreurs ; d’autres des lectures obsolètes ; quelques-uns ont vu des mises à jour partielles bizarres qui disparaissaient au rafraîchissement.

    L’on-call a fouillé les logs et trouvé des « database is locked » intermittents, mais pas constamment. Pire, il y avait des messages sporadiques de type « disk I/O error » qui ressemblaient à du matériel. Ils ne l’étaient pas. C’était le système de fichiers et le gestionnaire de verrous qui avaient un désaccord sur qui détenait la vérité à travers deux nœuds.

    L’hypothèse erronée était subtile : « Si le stockage est partagé, le verrou de fichier est partagé. » Sur beaucoup de systèmes de fichiers partagés, les verrous avisés ne se comportent pas comme des verrous ext4 locaux, surtout sous panne ou latence. SQLite n’était pas « cassé » ; l’environnement violait les hypothèses qu’il fait pour fournir les sémantiques ACID.

    La correction a été ennuyeuse : passer à Postgres sur un nœud d’abord, puis ajouter un réplica plus tard. Ils ont aussi retiré le montage partagé et traité les frontières de stockage comme des frontières de défaillance. Le rapport d’incident n’a pas blâmé SQLite ; il a blâmé l’architecture qui prétendait qu’un fichier pouvait être un système distribué.

    Mini-récit 2 : L’optimisation qui s’est retournée contre eux (Postgres réglé pour la vitesse, payé en anxiété de perte de données)

    Une autre organisation avait Postgres sur un petit VPS. Les écritures étaient lourdes : événements, logs, compteurs. L’équipe voulait une latence plus faible et a vu un article qui disait de désactiver des knobs de durabilité. Ils ont changé des paramètres pour réduire la pression fsync et ont rendu les commits plus rapides. Tout le monde a applaudi. Les graphs ont baissé.

    Deux semaines plus tard, l’hôte VPS a eu un reboot non planifié. Rien de dramatique—juste une de ces maintenances de nœud dont on apprend après coup. Postgres a redémarré, mais une tranche des écritures les plus récentes avait disparu. Pas catastrophique, mais suffisant pour déclencher des questions clients et des alarmes internes.

    Le vrai impôt est arrivé : l’incertitude. Ils ne pouvaient pas dire avec confiance ce qui avait été perdu, et les équipes produit ont commencé à traiter la base comme « peut-être cohérente ». C’est corrosif. Ça transforme chaque bug en débat sur la véracité des données.

    L’optimisation s’est retournée parce qu’elle optimisait la mauvaise chose : latence en état stable au prix d’une durabilité prévisible. Il y a des raisons valables de relâcher la durabilité pour de l’analytics éphémère ou des caches. Mais ils l’utilisaient pour de l’état client.

    La correction a été de restaurer des réglages sûrs pour les tables cœur, isoler les données à fort débit et faible valeur dans des chemins séparés, et exécuter des sauvegardes avec tests de restauration. Ils ont aussi introduit du batching pour réduire la fréquence des commits plutôt que de parier sur le comportement en cas de crash.

    Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise (drills de sauvegarde et automatisation de restauration)

    Celui-ci est moins dramatique, et c’est le but. Une équipe de SaaS sur un seul VPS utilisait Postgres. Ils n’étaient pas sophistiqués. Ils n’avaient pas d’équipe plateforme. Mais ils faisaient une chose sans relâche : des drills de restauration hebdomadaires vers une VM de test, avec une checklist.

    Ils avaient un script qui récupérait la dernière sauvegarde, la restaurait, exécutait une petite suite de requêtes de sanity, et confirmait que l’appli pouvait démarrer dessus. Ils gardaient aussi un « runbook » minimal décrivant comment promouvoir la DB restaurée si le primaire mourait. Personne n’aimait le faire. C’était comme se brosser les dents.

    Puis un développeur a lancé par erreur une migration destructive en prod. Pas malveillant. Juste une variable d’environnement mal réglée et un outil de migration qui a obéi. L’on-call a étouffé les alertes, juré en silence, et a démarré le drill de restauration qu’ils avaient pratiqué.

    Ils ont quand même eu une mauvaise heure, mais pas une mauvaise semaine. Ils ont restauré, relancé les migrations correctement, et rejoué une courte fenêtre d’événements business à partir des logs. Le PDG n’a jamais eu à apprendre ce que signifie « WAL », ce qui est le compliment le plus flatteur que les ops puissent recevoir.

    Citation (idée paraphrasée) : « On ne se hisse pas à la hauteur d’une occasion ; on retombe sur sa préparation. » — idée paraphrasée souvent entendue dans les cercles fiabilité/ops

    Listes de contrôle / plan pas-à-pas

    Checklist A : Si vous penchez pour SQLite (donnez-lui une forme production)

    1. Confirmez la réalité single-writer : listez tous les chemins de code qui écrivent (requêtes web, workers, cron, scripts admin). S’il y a plus d’un acteur à la fois, planifiez de sérialiser ou de migrer.
    2. Utilisez le mode WAL : définir PRAGMA journal_mode=WAL.
    3. Réglez synchronous de façon sensée : généralement NORMAL est un bon compromis sur VPS ; utilisez FULL si vous ne pouvez pas tolérer la perte d’écritures récentes en cas de crash.
    4. Définissez busy_timeout : faites en sorte que l’appli attende brièvement plutôt que d’échouer immédiatement en cas de contention de verrou.
    5. Sauvegardez correctement : utilisez le mécanisme de backup de SQLite, pas un « cp » du fichier en heures de pointe.
    6. Prévoyez la croissance du fichier : surveillez la taille du fichier DB et l’espace libre ; planifiez des VACUUM périodiques seulement si nécessaire.
    7. Ne mettez pas SQLite sur NFS/montages partagés : disque local uniquement, sauf si vous aimez déboguer les verrous de fichier à travers la latence.

    Checklist B : Si vous penchez pour PostgreSQL (rendez-le ennuyeux, stable et peu coûteux)

    1. Taille des connexions adaptée : gardez max_connections raisonnable ; utilisez un pooler pour les applis web.
    2. Réglez la mémoire délibérément : tunez shared_buffers de façon conservatrice sur petite RAM ; laissez de la marge pour le cache OS et votre appli.
    3. Activez la visibilité des requêtes : activez les stats de requêtes pour voir ce qui est lent avant que les utilisateurs ne se plaignent.
    4. Surveillez le vacuum : regardez les tuples morts et l’activité autovacuum ; le bloat est une fuite lente.
    5. Sauvegardes et tests de restauration : automatisez les deux. Une sauvegarde sans test de restauration n’est qu’un vœu.
    6. Planification des upgrades : décidez comment vous gèrerez les mises à jour mineures et majeures avant d’y être contraint.
    7. Gestion du disque : surveillez l’utilisation disque pour les données et le WAL ; évitez d’être à 90% plein sur un VPS.

    Pas-à-pas : le chemin décisionnel sans regret (15 minutes)

    1. Exécutez les Tasks 1–4 pour comprendre la réalité RAM et IO.
    2. Listez vos rédacteurs. S’il y a plus d’un rédacteur concurrent maintenant ou bientôt, choisissez Postgres.
    3. Si SQLite est encore plausible, lancez les Tasks 7–8. Si la contention de verrous apparaît lors d’un test de concurrence, choisissez Postgres.
    4. Si vous choisissez Postgres, exécutez les Tasks 9–12 et confirmez que vous pouvez le garder sain sur ce VPS.
    5. Exécutez la Task 15 et faites au moins un drill de restauration. Choisissez le système dont le chemin de restauration vous pouvez vraiment exécuter sous stress.

    Blague #2 : La base la plus rapide est celle que vous n’avez pas perdue à 03:00, ce qui est aussi pourquoi les sauvegardes ont le meilleur ROI de toute fonctionnalité que vous ne présenterez jamais en démo.

    FAQ

    1) SQLite peut-il gérer du trafic de production ?

    Oui, si « trafic de production » signifie principalement des lectures, un petit nombre d’écritures et un modèle de concurrence contrôlé. Il est utilisé dans beaucoup de systèmes réels. Il ne veut juste pas être votre coordinateur d’écritures multi-tenant.

    2) Le mode WAL rend-il SQLite « aussi bon que Postgres » ?

    Non. WAL réduit le blocage lecteur/rédacteur et améliore la concurrence, mais vous avez toujours un fichier de base unique avec une sémantique de verrouillage et moins d’outils de concurrence. Postgres est conçu comme un service partagé.

    3) Postgres est-il overkill pour un petit VPS ?

    Parfois. Si votre VPS est minuscule et que votre charge est simple, Postgres peut ajouter des pièces en mouvement supplémentaires. Mais si vous avez plusieurs rédacteurs ou une trajectoire de croissance, « overkill » devient vite « merci de ne pas m’avoir forcé à migrer en urgence ».

    4) Quel est le coût caché le plus important de Postgres sur un VPS ?

    La gestion des connexions et de la mémoire. Sans pooling et limites sensées, Postgres peut brûler de la RAM sur des sessions inactives et mourir d’une instabilité qui semble aléatoire. Ce n’est pas aléatoire ; c’est des mathématiques.

    5) Quel est le coût caché le plus important de SQLite sur un VPS ?

    La contention des verrous et les hypothèses opérationnelles. Au moment où vous avez plusieurs rédacteurs, des transactions longues, ou que vous placez le fichier sur un stockage douteux, vous héritez de modes de défaillance qui semblent mystérieux jusqu’à ce que vous acceptiez le modèle de verrouillage.

    6) Si je commence avec SQLite, la migration vers Postgres est-elle douloureuse ?

    Ça va de « un week-end » à « un trimestre », selon la complexité du schéma, le volume de données et combien votre appli s’appuyait sur des particularités SQLite. Si vous anticipez la croissance, concevez votre appli avec une abstraction DB et des outils de migration dès le premier jour.

    7) Dois-je utiliser SQLite pour le cache et Postgres pour la source de vérité ?

    Ça peut marcher, mais ne construisez pas un système distribué par accident. Si vous avez besoin d’un cache, envisagez des caches en mémoire ou des stratégies natives Postgres. Si vous utilisez SQLite comme cache local, traitez-le comme jetable et reconstruisable.

    8) Qu’en est-il de la durabilité : SQLite est-il dangereux ?

    SQLite peut être durable s’il est configuré correctement et utilisé sur un système de fichiers qui respecte ses attentes. Le risque n’est pas « SQLite est dangereux », c’est « SQLite vous permet facilement d’être dangereux sans le remarquer ». Postgres centralise ces comportements de durabilité dans un serveur conçu pour les crashes.

    9) Ai-je besoin de réplication sur un VPS ?

    Pas toujours. Pour beaucoup de setups VPS, le premier gain est des sauvegardes fiables et des drills de restauration. La réplication devient utile quand vos exigences d’uptime dépassent « restaurer en X minutes » et que vous pouvez assumer la complexité.

    10) Comment décider si mon appli a « plusieurs rédacteurs » ?

    Si des écritures peuvent se produire simultanément depuis plus d’un processus OS ou conteneur (workers web, job workers, tâches planifiées, scripts admin), vous avez plusieurs rédacteurs. Si vous déployez plusieurs instances d’appli, vous en avez définitivement.

    Prochaines étapes à faire aujourd’hui

    Choisissez une voie et rendez-la opérationnellement réelle. Les bases ne tombent pas en panne parce que vous avez choisi la mauvaise marque ; elles tombent en panne parce que vous n’avez pas adapté le système à la charge et que vous n’avez pas pratiqué la reprise.

    Si vous choisissez SQLite

    • Activez WAL et définissez explicitement synchronous.
    • Ajoutez un busy timeout et gardez les transactions courtes.
    • Implémentez des sauvegardes via le mécanisme de backup de SQLite et faites un test de restauration.
    • Écrivez une règle stricte : « pas de filesystem partagé, pas de chaos multi-rédacteur. »

    Si vous choisissez PostgreSQL

    • Mettez en place un pooling de connexions et des limites sensées immédiatement.
    • Activez la visibilité des requêtes et surveillez les requêtes lentes et les verrous.
    • Automatisez les sauvegardes et effectuez des drills de restauration selon un calendrier.
    • Surveillez l’utilisation disque et la santé du vacuum avant d’en avoir besoin.

    La version sans regrets ne consiste pas à choisir la « meilleure » base. Il s’agit de choisir la base dont les modes de défaillance vous sont prévisibles, observables et récupérables sur un VPS pendant des heures adaptées aux humains.

    Pentium 4 / NetBurst : l’erreur la plus retentissante de l’ère des GHz

    Si vous exploitiez des systèmes en production au début des années 2000, vous vous souvenez probablement de la sensation : vous achetiez « plus de GHz »,
    vos graphiques ne s’amélioraient pas, et votre beeper si. La latence restait rude, le débit plafonnait, et les ventilateurs
    trouvaient de nouvelles façons de hurler.

    NetBurst (la microarchitecture du Pentium 4) est une étude de cas sur ce qui arrive quand marketing et microarchitecture
    se serrent trop la main. Ce n’est pas que les ingénieurs étaient incompétents. C’est que les contraintes étaient brutales,
    le pari était étroit, et le monde réel a refusé de coopérer.

    Thèse : les GHz étaient un indicateur, pas un produit

    NetBurst a été conçu pour la fréquence. Pas « une bonne fréquence », pas « une fréquence efficace », mais « mettre le chiffre sur la boîte
    et laisser le monde débattre après » fréquence. Intel venait de passer des années à entraîner les clients à interpréter la vitesse d’horloge
    comme performance. Le marché récompensait cette simplification. Puis les factures sont arrivées : des pipelines d’instructions si profonds
    que les mauvaises prédictions coûtent cher, un sous-système mémoire qui ne suivait pas, et une densité de puissance qui transformait
    la conception des racks en hobby pour les passionnés de CVC.

    Ce n’était pas un seul mauvais choix de conception. C’était un empilement de compromis qui supposaient tous une chose :
    les fréquences continueraient d’augmenter, et les logiciels joueraient le jeu. Quand l’une ou l’autre hypothèse a échoué — code branché, charges lourdes
    en mémoire, contraintes réalistes de datacenter — toute l’approche s’est effondrée.

    Si vous voulez la traduction SRE : NetBurst optimisait le pic sous microbenchmarks idéaux et punissait la latence de queue
    sous charge mixte en production. C’est une façon de livrer beaucoup de déception.

    Exactement une fois, j’ai vu une diapositive d’achat traiter « 3.0 GHz » comme si c’était un SLA de débit.
    C’est comme estimer la performance réseau en comptant les lettres de « Ethernet ».

    Internes de NetBurst : le pipeline qui a mangé votre IPC

    Pipelines profonds : excellent pour la fréquence, terrible pour les erreurs

    L’histoire classique de NetBurst est « pipeline très profond ». L’histoire pratique est « pénalité de mauvaise prédiction élevée ».
    Un pipeline plus profond vous aide à atteindre des fréquences plus élevées parce que chaque étape fait moins de travail. L’inconvénient est que vous avez maintenant
    étiré la distance entre « on a deviné la branche » et « on découvre qu’on s’est trompé ». Quand on se trompe, on efface beaucoup de travail en vol et on recommence.

    Les CPUs modernes pipelinent encore en profondeur, mais ils remboursent cela avec de meilleurs prédicteurs, des caches plus grands, une exécution plus large,
    et une gestion de l’énergie soigneuse. NetBurst est allé profond tôt, avec des prédicteurs et des systèmes mémoire qui ne pouvaient pas complètement
    couvrir le pari sur les parcours de code typiques des serveurs.

    Trace cache : astucieux, complexe et sensible à la charge

    Le trace cache de NetBurst stockait des micro-opérations décodées (uops), pas des instructions x86 brutes. C’était intelligent : décoder le x86 n’est
    pas trivial, et un cache de uops peut réduire le coût d’avant-garde. Mais cela rendait aussi la performance plus dépendante de la façon dont le code
    s’écoulait et s’alignait. Si votre flux d’instructions ne se comportait pas bien — beaucoup de branches, dispositions étranges, mauvaise localité — le
    trace cache cessait d’être un cadeau et devenait un autre endroit où manquer.

    L’idée n’était pas fausse ; elle était précoce et fragile. Les uop caches d’aujourd’hui réussissent parce que le reste du système s’est amélioré pour les alimenter,
    et parce que les compromis puissance/perf sont gérés avec plus de finesse.

    FSB et northbridge partagé : le péage de la bande passante

    Les systèmes Pentium 4 reposaient sur un front-side bus (FSB) vers un contrôleur mémoire séparé (northbridge). Cela signifie que votre
    cœur CPU est rapide, votre mémoire est « ailleurs », et chaque requête est un voyage sur un bus partagé. Sous charge, ce bus devient un problème d’ordonnancement.
    Ajoutez plusieurs CPUs et c’est un travail d’équipe.

    Comparez cela aux conceptions ultérieures avec contrôleurs mémoire intégrés (AMD l’a fait plus tôt sur x86 ; Intel a suivi). Quand
    vous rapprochez la mémoire et lui donnez des chemins plus dédiés, vous réduisez la contention et baissez la latence. En production,
    la latence est de la monnaie. NetBurst l’a dépensée comme un touriste.

    Ère SSE2/SSE3 : fort en calcul streaming, inégal ailleurs

    NetBurst s’en sortait bien dans certaines charges vectorisées et streaming — code qui pouvait traiter des tableaux de façon prévisible et éviter la logique branchée.
    Voilà pourquoi les benchmarks pouvaient sembler corrects s’ils avaient été conçus pour alimenter la machine avec le type de travail qu’elle aimait. Mais les services réels ne sont pas polis.
    Ils parsèment, branchent, allouent, verrouillent et attendent des E/S.

    NetBurst était l’équivalent CPU d’un moteur réglé pour un circuit spécifique. Mettez-le en ville et vous apprendrez ce que signifie « courbe de couple ».

    Pourquoi les charges réelles souffrent : caches, branches, mémoire et attente

    IPC est ce que vous ressentez ; les GHz sont ce dont vous vous vantez

    Instructions par cycle (IPC) est un proxy brutal mais utile pour « combien de travail est fait à chaque tick ». NetBurst avait souvent
    un IPC inférieur à ses contemporains dans de nombreuses charges à usage général. Ainsi la puce tournait à une fréquence plus élevée pour compenser. Cela peut
    marcher — jusqu’à ce que ça ne marche plus, parce que :

    • Le code branché déclenche des mauvaises prédictions, qui coûtent plus cher dans les pipelines profonds.
    • Les défauts de cache bloquent l’exécution, et un cœur rapide atteint simplement le blocage plus tôt.
    • La latence FSB/mémoire devient un mur solide que vous ne pouvez pas contourner par horloge.
    • La puissance/thermique force la réduction de fréquence, donc les GHz annoncés sont aspiratoires.

    Mauvaise prédiction de branche : la taxe de latence que vous payez en continu

    Les charges serveurs sont pleines de branches imprévisibles : routage de requêtes, parsing, vérifications d’autorisation, recherches dans des tables de hachage,
    appels virtuels, décisions de compression, chemins d’exécution de bases de données. Quand les prédicteurs échouent, les pipelines profonds perdent
    du travail et du temps. Le CPU ne « ralentit » pas. Il fait juste moins de travail utile tout en restant très occupé.

    Mur mémoire : quand le cœur dépasse le système

    NetBurst pouvait exécuter rapidement quand on le nourrissait, mais beaucoup de charges sont limitées par la mémoire. Un défaut de cache, ce sont des centaines de cycles
    d’attente. Ce chiffre n’est pas une faute morale ; c’est de la physique plus de la topologie. L’effet pratique est qu’un CPU avec plus de GHz peut paraître pire s’il atteint
    des stalls mémoire plus fréquemment ou ne peut pas les masquer efficacement.

    Du point de vue de l’opérateur, cela se manifeste par : forte utilisation CPU, débit médiocre, et un système qui semble « bloqué » sans saturation évidente des E/S.
    Ce n’est pas bloqué. Il attend la mémoire et se fight lui-même.

    Exécution spéculative : utile, mais elle amplifie le coût des mauvaises hypothèses

    La spéculation est la façon dont les CPUs modernes obtiennent des performances : deviner un chemin, l’exécuter, jeter si c’est faux. Dans un pipeline profond,
    le mauvais chemin est cher. Le pari de NetBurst était que de meilleures horloges paieraient pour cela. Parfois ça marchait. Souvent, non.

    L’une des leçons opérationnelles les plus simples de l’ère NetBurst : ne considérez pas « CPU à 95 % » comme « CPU fait 95 % de travail utile ».
    Vous avez besoin de compteurs, pas d’impressions.

    Thermique et puissance : quand votre CPU négocie avec la physique

    La densité de puissance est devenue une caractéristique produit (par accident)

    NetBurst chauffait. Surtout les Pentium 4 basés sur Prescott, qui sont devenus infâmes pour leur consommation et leur chaleur. La chaleur n’est pas seulement une facture d’électricité ; c’est un risque de fiabilité,
    du bruit de ventilateurs et de la variabilité des performances.

    En production, les cartes thermiques se transforment en cartes d’incident. Si une conception pousse fortement le refroidissement, votre marge disparaît :
    filtres poussiéreux, ventilateur défaillant, une grille bouchée, une allée chaude qui dérive, ou un rack poussé trop près d’un mur devient un événement de performance. Et les événements de performance deviennent des événements de disponibilité.

    Réduction thermique : le frein invisible

    Lorsqu’un CPU réduit sa fréquence, l’horloge change, l’exécution change, et la latence de queue de votre service bascule de façons que vos tests de charge n’avaient jamais modélisées.
    Avec les systèmes de l’ère NetBurst, il n’était pas rare de voir « le benchmark dit X » mais « la prod fait Y » parce que les conditions ambiantes n’étaient pas contrôlées comme en laboratoire.

    Blague #1 : Prescott n’était pas un radiateur de remplacement, mais il rendait les astreintes hivernales un peu plus tolérables si vous étiez assis près du rack.

    Fiabilité et exploitation : les systèmes chauds vieillissent plus vite

    Condensateurs, VRM, ventilateurs et cartes mères n’aiment pas la chaleur. Même s’ils survivent, ils dérivent. Cette dérive devient des erreurs intermittentes, des redémarrages spontanés,
    et des histoires de type « ça marche après qu’on l’ait reposé ». Ce n’est pas du mysticisme ; c’est dilatation thermique, alimentation marginale, et composants quittant leur zone de confort.

    Une idée paraphrasée souvent attribuée à W. Edwards Deming s’applique clairement aux ops : « Vous ne pouvez pas gérer ce que vous ne mesurez pas. » Avec NetBurst, il fallait mesurer les thermiques,
    parce que le CPU le faisait clairement.

    Hyper-Threading : l’astuce utile qui a exposé de mauvaises hypothèses

    L’Hyper-Threading (SMT) est arrivé sur certains modèles de Pentium 4 et était légitimement utile dans les bonnes conditions :
    il pouvait remplir les bulles du pipeline en exécutant un autre thread quand un était en stall. Ça ressemble à de la performance gratuite,
    et parfois ça l’était.

    Quand ça aidait

    • Charges mixtes où un thread attend des défauts de cache et l’autre peut utiliser les unités d’exécution.
    • Services I/O lourds où un thread se bloque fréquemment et où le coût du scheduler est gérable.
    • Certaines fonctions serveurs orientées débit avec requêtes indépendantes et faible contention sur les verrous.

    Quand ça nuisait

    • Charges limitées par la bande passante mémoire : deux threads se battent juste plus fort pour le même goulot.
    • Charges avec verrous lourds : plus de contention, plus de bouncing de lignes de cache, pire latence de queue.
    • Services sensibles à la latence : jitter lié aux ressources partagées et artefacts d’ordonnancement.

    L’Hyper-Threading sur NetBurst est un bon microcosme d’une règle plus large : le SMT améliore de bons designs et rend les designs fragiles plus bizarres.
    Il peut accroître le débit tout en rendant la latence plus laide. Si votre SLO est p99, vous ne l’« activez et priez » pas. Vous le testez avec une concurrence proche de la production et vous vérifiez la queue.

    Faits historiques importants (et quelques piqûres qui restent)

    1. NetBurst a débuté avec Willamette (Pentium 4, 2000), priorisant la vitesse d’horloge sur l’IPC.
    2. Northwood a amélioré l’efficacité et les fréquences, et est devenu le Pentium 4 « moins douloureux » pour beaucoup d’acheteurs.
    3. Prescott (2004) est passé à un processus plus petit, a ajouté des fonctionnalités, et est devenu notoire pour la chaleur et la consommation.
    4. La « course aux GHz » a fortement modelé les décisions d’achat au point que « horloge plus élevée » battait souvent une meilleure architecture dans les ventes.
    5. L’accès mémoire basé FSB signifiait que le CPU rivalisait pour la bande passante sur un bus partagé vers le northbridge.
    6. Le trace cache stockait des micro-ops décodés, visant à réduire le coût du décodage et à alimenter efficacement le long pipeline.
    7. Hyper-Threading est arrivé sur certains modèles et pouvait améliorer le débit en utilisant des ressources d’exécution inoccupées.
    8. Pentium M (dérivé de la lignée P6) surpassait souvent le Pentium 4 à des fréquences bien plus basses, surtout dans les tâches réelles.
    9. Intel a finalement pivoté loin de NetBurst ; Core (basé sur une lignée différente) a remplacé la stratégie plutôt que de l’itérer indéfiniment.

    Trois mini-histoires d’entreprise sorties des tranchées

    Mini-histoire 1 : l’incident causé par une mauvaise hypothèse (« GHz = capacité »)

    Une entreprise de taille moyenne a hérité d’une flotte de serveurs web vieillissants et prévu un rafraîchissement rapide. Les critères de sélection étaient
    douloureusement simples : choisir les boîtiers Pentium 4 à la fréquence la plus élevée dans le budget. La note d’achat a littéralement assimilé
    « +20 % d’horloge » à « +20 % de requêtes par seconde ». Personne n’était malveillant ; ils étaient occupés.

    Le déploiement s’est bien passé jusqu’à ce que le trafic atteigne son pic habituel. L’utilisation CPU semblait correcte — élevée mais stable.
    Le réseau était sous contrôle. Les disques ne hurlaient pas. Pourtant la latence p95 a augmenté, puis le p99 est parti verticalement. L’équipe d’astreinte
    a fait ce que font les équipes : redémarré des services, déplacé le trafic, blâmé le load balancer, et regardé les graphiques jusqu’à ce que les graphiques les regardent en retour.

    Le vrai problème était le comportement mémoire. La charge avait évolué au fil des ans : plus de personnalisation, plus de logique de template, plus de routage dynamique.
    Cela signifiait plus de chasing de pointeurs et de branchements. Les nouveaux serveurs avaient des horloges plus élevées mais une latence mémoire similaire et une topologie FSB partagée
    qui empirait sous concurrence. Ils atteignaient plus vite les mêmes stalls mémoire, et l’Hyper-Threading ajoutait de la contention au pire moment.

    La solution n’était pas « tuner Linux davantage ». La solution a été de rebaseliner la capacité en utilisant un test proche de la production :
    concurrence réaliste, phases cache-chaud et cache-froid, et la latence de queue comme métrique de première classe. L’entreprise a fini par modifier la composition de la flotte :
    moins de machines « haute fréquence », plus de nœuds équilibrés avec de meilleurs sous-systèmes mémoire. Ils ont aussi arrêté d’utiliser les GHz comme principal nombre de capacité.
    Des miracles arrivent quand vous arrêtez de vous mentir.

    Mini-histoire 2 : l’optimisation qui a échoué (« activer HT pour obtenir des performances gratuites »)

    Une autre équipe faisait tourner un service Java avec beaucoup de requêtes courtes. Ils ont activé l’Hyper-Threading sur toute la flotte
    et doublé les threads travailleurs, s’attendant à des gains de débit linéaires. Les premiers tests synthétiques semblaient excellents. Puis les rapports d’incident sont arrivés :
    pics de latence sporadiques, pauses GC qui coïncidaient avec des rafales de trafic, et un nouveau type de « c’est lent mais rien n’est saturé ».

    Le système n’était pas en panne CPU ; il était en panne de cache et de verrous. Deux CPU logiques partageaient des ressources d’exécution
    et, plus important, le cache partagé et les chemins de bande passante mémoire. Les patterns d’allocation et de synchronisation de la JVM créaient du bouncing de lignes de cache,
    et la concurrence supplémentaire a amplifié la contention dans des hotspots auparavant insignifiants.

    Ils ont essayé de corriger en augmentant la taille du heap, en affinant l’affinité des threads, puis en tournant des boutons qui faisaient « système ». Certains ont aidé, la plupart non.
    Le vrai gain est venu en prenant du recul : traiter l’Hyper-Threading comme un outil de débit avec un coût en latence. Mesurez le coût.

    Ils sont revenus à moins de threads workers, ont activé HT uniquement sur les nœuds servant du batch non interactif, et ont utilisé le profilage applicatif pour supprimer quelques goulots de verrous.
    Le débit a fini légèrement supérieur à l’avant « optimisation », et la latence de queue est redevenue ennuyeuse. La leçon n’était pas « HT est mauvais ». La leçon était « HT est un multiplicateur, et il multiplie aussi vos erreurs ».

    Mini-histoire 3 : la pratique ennuyeuse mais correcte qui a sauvé la mise (« marge thermique = capacité »)

    Une équipe de services financiers exécutait des jobs nocturnes intensifs sur un cluster incluant des nœuds Pentium 4 de l’ère Prescott. Personne n’aimait ces boîtiers,
    mais les jobs étaient stables et le cluster « suffisait ». Le super-pouvoir discret de l’équipe était qu’ils traitaient l’environnement comme faisant partie de la capacité :
    surveillance de la température d’entrée d’air, contrôles de santé des ventilateurs, et alertes sur les indicateurs de throttling thermique.

    Un été, une unité de refroidissement s’est dégradée pendant un week-end. Pas une panne complète — juste une sous-performance. Lundi matin, les durées de job ont augmenté lentement.
    La plupart des équipes auraient blâmé le scheduler ou la base de données. Cette équipe a remarqué une corrélation subtile : des nœuds dans une rangée montraient des relevés thermiques légèrement plus élevés
    et une fréquence CPU effective légèrement plus basse.

    Ils ont vidangé ces nœuds, déplacé les jobs vers des racks plus frais, et ouvert un ticket facilities avec des preuves concrètes. Ils ont aussi réduit temporairement la concurrence par nœud
    pour diminuer la production de chaleur et stabiliser les temps d’exécution. Pas de drame, pas d’héroïsme, pas de salle de crise à minuit.

    Le résultat : les jobs ont fini à l’heure, pas d’incident côté client, et le problème de refroidissement a été réparé avant qu’il ne devienne une fête des défaillances matérielles.
    La pratique était ennuyeuse — mesurer les thermiques, surveiller le throttling, maintenir une marge — mais elle a transformé un « ralentissement mystérieux » en un changement contrôlé. L’ennuyeux est sous-estimé.

    Tâches pratiques : 12+ commandes pour diagnostiquer « CPU rapide, système lent »

    Celles-ci sont exécutables sur un serveur Linux typique. Vous n’essayez pas de « prouver que NetBurst est mauvais » en 2026.
    Vous apprenez à reconnaître les mêmes modes de défaillance : stalls de pipeline, mur mémoire, artefacts d’ordonnancement,
    throttling thermique, et utilisation trompeuse.

    Task 1: Identify the CPU and whether HT is present

    cr0x@server:~$ lscpu
    Architecture:            x86_64
    CPU op-mode(s):          32-bit, 64-bit
    CPU(s):                  2
    Thread(s) per core:      2
    Core(s) per socket:      1
    Socket(s):               1
    Model name:              Intel(R) Pentium(R) 4 CPU 3.00GHz
    Flags:                   fpu vme de pse tsc ... ht ... sse2

    Ce que cela signifie : « Thread(s) per core: 2 » indique l’Hyper-Threading. Le nom du modèle vous donne la famille.

    Décision : Si HT est présent, faites des benchmarks avec HT activé/désactivé pour les services sensibles à la latence ; n’assumez pas que c’est un gain.

    Task 2: Check current frequency and scaling driver

    cr0x@server:~$ grep -E 'model name|cpu MHz' /proc/cpuinfo | head
    model name	: Intel(R) Pentium(R) 4 CPU 3.00GHz
    cpu MHz		: 2793.000

    Ce que cela signifie : Le CPU n’est pas à sa fréquence nominale. Cela peut être de l’économie d’énergie ou du throttling.

    Décision : Si la fréquence est anormalement basse sous charge, investiguez les gouverneurs et le throttling thermique ensuite.

    Task 3: Confirm the CPU frequency governor

    cr0x@server:~$ cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
    ondemand

    Ce que cela signifie : « ondemand » peut réduire la fréquence jusqu’à ce que la charge augmente ; sur les plateformes plus anciennes il peut répondre lentement.

    Décision : Pour les services à faible latence, envisagez « performance » et retestez ; pour du batch, « ondemand » peut convenir.

    Task 4: Look for thermal zones and temperatures

    cr0x@server:~$ for z in /sys/class/thermal/thermal_zone*/temp; do echo "$z: $(cat $z)"; done
    /sys/class/thermal/thermal_zone0/temp: 78000
    /sys/class/thermal/thermal_zone1/temp: 65000

    Ce que cela signifie : Les températures sont en millidegrés Celsius. 78000 = 78°C.

    Décision : Si les températures approchent les seuils de throttling pendant le pic, traitez le refroidissement comme un limiteur de capacité, pas comme une « anecdote facilities ».

    Task 5: Detect throttling indicators in kernel logs

    cr0x@server:~$ dmesg | grep -i -E 'throttl|thermal|critical|overheat' | tail
    CPU0: Thermal monitoring enabled (TM1)
    CPU0: Temperature above threshold, cpu clock throttled
    CPU0: Temperature/speed normal

    Ce que cela signifie : Le CPU a réduit sa vitesse à cause de la chaleur. Votre « mystère » de débit peut être de la simple physique.

    Décision : Corrigez le flux d’air/le refroidissement, réduisez la charge ou réduisez la concurrence. Ne touchez pas au logiciel pour compenser un défaut thermique.

    Task 6: Check run queue and CPU saturation quickly

    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 120000  15000 210000    0    0     2     5  900 1400 85 10  5  0  0
     4  0      0 118000  15000 209000    0    0     0     8 1100 1800 92  7  1  0  0

    Ce que cela signifie : « r » (file d’exécution) constamment au-dessus du nombre de CPU implique une contention CPU. Un « id » bas signifie occupé.

    Décision : Si la file d’exécution est élevée, vous êtes saturé CPU ou en stall. Ensuite : déterminer si c’est calcul, mémoire ou verrous.

    Task 7: Identify top CPU consumers and whether they’re spinning

    cr0x@server:~$ top -b -n 1 | head -n 15
    top - 12:14:01 up 21 days,  3:11,  1 user,  load average: 3.90, 3.60, 3.20
    Tasks: 184 total,   2 running, 182 sleeping,   0 stopped,   0 zombie
    %Cpu(s): 92.0 us,  7.0 sy,  0.0 ni,  1.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
    2174 app      20   0  1856m  612m  122m R  98.7  7.6  12:11.02 java

    Ce que cela signifie : Un CPU utilisateur élevé peut quand même être « en attente » (stalls mémoire, mauvaises prédictions). Un « wa » bas n’invalide pas l’occupation CPU.

    Décision : Si un processus domine, profilez-le ; si beaucoup partagent, suspectez une contention systémique ou des effets du scheduler.

    Task 8: Check context switching and interrupt pressure

    cr0x@server:~$ pidstat -w 1 5
    Linux 6.1.0 (server) 	01/09/2026 	_x86_64_	(2 CPU)
    
    12:14:20      UID       PID   cswch/s nvcswch/s  Command
    12:14:21     1001      2174   1200.00    350.00  java

    Ce que cela signifie : Des switches de contexte très élevés peuvent impliquer trop de threads, de la contention sur les verrous, ou du churn timer/IO.

    Décision : Si cswch/s est élevé et la latence mauvaise, réduisez le nombre de threads ou corrigez les hotspots de verrou avant d’acheter du matériel.

    Task 9: Look at memory bandwidth stress signals via perf (cycles vs stalled frontend/backend)

    cr0x@server:~$ sudo perf stat -p 2174 -e cycles,instructions,branches,branch-misses,cache-misses -a -- sleep 10
     Performance counter stats for 'system wide':
    
         32,100,000,000      cycles
         12,400,000,000      instructions              #    0.39  insn per cycle
          2,900,000,000      branches
            145,000,000      branch-misses             #    5.00% of all branches
            610,000,000      cache-misses

    Ce que cela signifie : Un IPC bas (0.39) plus des défauts cache/branche élevés suggèrent que le CPU est occupé mais peu productif.

    Décision : Optimisez les chemins de code pour la localité/la prédictibilité des branches, ou choisissez un CPU avec meilleur IPC et gestion mémoire.

    Task 10: Confirm whether you’re actually I/O bound

    cr0x@server:~$ iostat -xz 1 3
    avg-cpu:  %user   %nice %system %iowait  %steal   %idle
              90.50    0.00    7.20    0.10    0.00    2.20
    
    Device            r/s     w/s   rkB/s   wkB/s  rrqm/s  wrqm/s  %util  await
    sda              2.10    1.00   45.0    28.0     0.0     0.2    3.0    1.2

    Ce que cela signifie : Le disque est peu utilisé ; await est bas. Ce n’est pas un goulot de stockage.

    Décision : Arrêtez de blâmer les disques. Concentrez-vous sur CPU/mémoire/verrouillage et le profilage au niveau requête.

    Task 11: Check memory pressure and paging (the silent performance killer)

    cr0x@server:~$ free -m
                   total        used        free      shared  buff/cache   available
    Mem:            2048        1720         120          12         207         210
    Swap:           2048         900        1148

    Ce que cela signifie : L’utilisation du swap peut être acceptable, mais si du paging actif survient sous charge vous verrez des stalls et des pics.

    Décision : Si l’activité swap corrèle avec la latence, réduisez l’empreinte mémoire, ajoutez de la RAM, ou ajustez le placement des charges.

    Task 12: Verify active paging, not just swap usage

    cr0x@server:~$ sar -B 1 5
    Linux 6.1.0 (server) 	01/09/2026 	_x86_64_	(2 CPU)
    
    12:15:10  pgpgin/s pgpgout/s   fault/s  majflt/s  pgfree/s pgscank/s pgsteal/s
    12:15:11      0.00      0.00    820.00      0.00   1200.00      0.00      0.00
    12:15:12     10.00     45.00   2100.00     15.00    400.00    800.00    300.00

    Ce que cela signifie : Les fautes majeures (majflt/s) et le scanning indiquent une vraie pression mémoire.

    Décision : Le paging sous charge est un problème de capacité. Réparez la mémoire, pas les flags CPU.

    Task 13: Inspect scheduler pressure at a glance

    cr0x@server:~$ cat /proc/pressure/cpu
    some avg10=12.34 avg60=10.01 avg300=8.55 total=987654321

    Ce que cela signifie : Le PSI CPU « some » indique le temps que les tâches passent à attendre des ressources CPU.

    Décision : Si le PSI monte avec la latence, vous avez besoin d’un CPU plus efficace (IPC), de moins de threads exécutables, ou d’un écrêtage de charge.

    Task 14: Detect lock contention (often misdiagnosed as “slow CPU”)

    cr0x@server:~$ sudo perf top -p 2174
    Samples: 31K of event 'cpu-clock', 4000 Hz, Event count (approx.): 7750000000
    Overhead  Shared Object        Symbol
      12.40%  libc.so.6            [.] pthread_mutex_lock
       9.10%  libjvm.so            [.] SpinPause

    Ce que cela signifie : Le temps est passé dans les verrous et le spinning, pas dans du travail productif.

    Décision : Réduisez la contention (sharder les verrous, réduire les threads, corriger les sections critiques chaudes). Plus de GHz ne vous sauvera pas.

    Task 15: Validate cache friendliness via a quick microbenchmark stance (not a substitute for real tests)

    cr0x@server:~$ taskset -c 0 sysbench cpu --cpu-max-prime=20000 run
    CPU speed:
        events per second:  580.21
    
    General statistics:
        total time:                          10.0004s
        total number of events:              5804

    Ce que cela signifie : Un test fortement compute peut paraître « correct » même si votre service est limité par la mémoire/les branches.

    Décision : Utilisez les microbenchmarks uniquement pour une validation de base ; basez vos décisions sur des tests représentatifs de la charge et sur la latence.

    Blague #2 : Si votre plan est « ajouter des threads jusqu’à ce que ça aille vite », vous n’optimisez pas — vous invoquez des démons de contention.

    Roue de diagnostic rapide : ce qu’il faut vérifier d’abord/deuxièmement/troisièmement

    Voici le raccourci orienté production pour les surprises de type NetBurst : systèmes qui paraissent « riches en CPU » sur le papier mais deviennent lents sous charges réelles.
    Vous voulez le goulot vite, pas un débat philosophique sur la microarchitecture.

    Premier : vérifiez que le CPU que vous pensez avoir est bien celui que vous obtenez

    1. Fréquence sous charge : vérifiez /proc/cpuinfo MHz, le gouverneur de scaling, et dmesg pour le throttling.
    2. Thermiques : vérifiez les zones thermiques et l’état des ventilateurs/flux d’air via la télémétrie disponible.
    3. Virtualisation : confirmez que vous n’êtes pas limité par des quotas CPU ou des voisins bruyants (PSI, cgroups).

    Objectif : éliminer « le CPU ne tourne littéralement pas à la vitesse attendue » en moins de 5 minutes.

    Second : déterminez si vous êtes lié au calcul, à la mémoire, ou à la contention

    1. File d’exécution et PSI : vmstat et /proc/pressure/cpu pour l’attente CPU.
    2. IPC via perf : cycles vs instructions ; un IPC bas suggère stalls/défauts.
    3. Signaux de contention sur les verrous : perf top, pidstat context switches, dumps de threads applicatifs.

    Objectif : classer la douleur. Vous ne pouvez pas réparer ce que vous ne nommez pas.

    Troisième : confirmez que ce n’est pas I/O et pas du paging

    1. Disque : iostat -xz pour l’utilisation et l’await.
    2. Paging : sar -B pour les fautes majeures et le scanning.
    3. Réseau : vérifiez les drops/erreurs et l’encombrement des files (non montrés ci-dessus, mais à faire).

    Objectif : arrêter de perdre du temps sur le mauvais sous-système.

    Quatrième : décidez si c’est un problème d’adéquation matériel ou logiciel

    • Si l’IPC est bas à cause des défauts de cache et des mauvaises prédictions, vous avez besoin d’une meilleure localité ou d’un CPU avec meilleur IPC — pas plus de GHz.
    • Si la contention domine, réduisez la concurrence ou redessinez les chemins chauds — les upgrades matériels ne résoudront pas du code sérialisé.
    • Si du throttling est présent, corrigez le refroidissement et l’alimentation d’abord ; sinon chaque autre changement est du bruit.

    Erreurs courantes : symptômes → cause racine → correctif

    1) Symptom: “CPU is pegged, but throughput is mediocre”

    Cause racine : IPC bas dû à des défauts de cache, des mauvaises prédictions ou la latence mémoire ; le CPU paraît occupé mais est en stall.

    Correctif : Utilisez perf stat pour confirmer un IPC bas et des défauts élevés ; optimisez la localité, réduisez le pointer chasing, et profilez les chemins chauds. Si vous achetez du matériel, priorisez l’IPC et le sous-système mémoire, pas l’horloge.

    2) Symptom: “Latency spikes appear only during warm afternoons / after a fan replacement”

    Cause racine : Throttling thermique ou mauvais flux d’air provoquant des baisses de fréquence et du jitter.

    Correctif : Confirmez via dmesg et les lectures des zones thermiques ; remédiez au refroidissement, nettoyez les filtres, vérifiez les courbes des ventilateurs, et conservez une marge de température d’entrée. Traitez les thermiques comme une dépendance SLO de première classe.

    3) Symptom: “We enabled Hyper-Threading and p99 got worse”

    Cause racine : Contention des ressources partagées (unités d’exécution/caches), augmentation de la contention sur les verrous, ou saturation de la bande passante mémoire.

    Correctif : Testez A/B HT on/off avec une concurrence proche de la production ; réduisez le nombre de threads ; corrigez les hotspots de verrous ; envisagez HT uniquement pour les charges orientées débit ou bloquées I/O.

    4) Symptom: “Microbenchmarks improved, production got slower”

    Cause racine : Les microbenchmarks sont compute-heavy et prévisibles ; la production est branchée et mémoire-intensive. Les designs type NetBurst récompensent le premier et punissent le second.

    Correctif : Benchmarchez avec des mix de requêtes réalistes, phases cache-chaud/froid, et la latence de queue. Incluez concurrence, comportement de l’allocateur, et tailles de données réalistes.

    5) Symptom: “Load average increased after we ‘optimized’ by adding threads”

    Cause racine : Sur-souscription et contention ; plus de threads exécutables augmentent l’ordonnancement et le coût des verrous.

    Correctif : Utilisez pidstat pour mesurer les context switches, perf top pour les symboles de verrou, et réduisez la concurrence. Ajoutez du parallélisme seulement là où le travail est réellement parallèle et où le goulot se déplace.

    6) Symptom: “CPU upgrades didn’t help the database”

    Cause racine : La charge est liée à la latence mémoire ou à la bande passante mémoire (défauts du buffer pool, pointer chasing dans les B-trees, défauts de cache).

    Correctif : Augmentez le hit rate effectif du cache (indexes, forme des requêtes), ajoutez de la RAM, réduisez le working set, et mesurez les défauts de cache/IPC. Ne jetez pas des GHz contre un mur mémoire.

    7) Symptom: “Everything looks fine except occasional pauses and timeouts”

    Cause racine : Paging, pauses GC, ou pics de contention qui n’apparaissent pas comme une utilisation soutenue.

    Correctif : Vérifiez les fautes majeures, le PSI, et les métriques de pause applicatives. Corrigez la pression mémoire et réduisez l’amplification de queue (timeouts, retries, thundering herds).

    Listes de contrôle / plan pas à pas

    Checklist A: Buying hardware without repeating the NetBurst mistake

    1. Définir le succès par la latence et le débit (p50/p95/p99 + RPS soutenu), pas par la vitesse d’horloge.
    2. Mesurer des proxies d’IPC : utilisez perf sur des charges représentatives ; comparez cycles/instructions et taux de défaut.
    3. Modéliser le comportement mémoire : taille du working set, taux de hits cache, concurrence prévue, et besoins de bande passante.
    4. Valider les thermiques : testez en rack, avec température ambiante réaliste et profils de ventilateurs.
    5. Tester l’impact SMT/HT : on/off, avec nombres de threads réels et suivi de la latence de queue.
    6. Privilégier les systèmes équilibrés : canaux mémoire, tailles de cache et interconnexions comptent autant que les fréquences des cœurs.

    Checklist B: When a “faster CPU” deployment makes production slower

    1. Confirmer la fréquence et le throttling (gouverneur, températures, dmesg).
    2. Comparer l’IPC et les taux de défaut via perf avant/après.
    3. Vérifier les nombres de threads et le context switching ; rollback des changements « double threads » en priorité.
    4. Valider la pression mémoire et le paging ; corriger les fautes majeures immédiatement.
    5. Rechercher des régressions de contention de verrous introduites par la nouvelle concurrence.
    6. Si encore obscur, capturez un flame graph ou équivalent et examinez-le comme une timeline d’incident.

    Checklist C: Stabilize tail latency on old, hot, frequency-chasing systems

    1. Réduire la concurrence pour correspondre aux cœurs (surtout avec HT) et observer l’impact sur le p99.
    2. Pinnez les threads critiques uniquement si vous comprenez votre topologie ; sinon vous risquez de vous enfermer.
    3. Maintenez un gouverneur CPU cohérent (souvent « performance » pour les nœuds critiques en latence).
    4. Faites respecter la marge thermique : alertez sur la température et les événements de throttling, pas seulement sur l’utilisation CPU.
    5. Optimisez les chemins chauds pour la localité ; éliminez les branches imprévisibles quand c’est possible.
    6. Introduisez du backpressure et des timeouts sensés pour éviter les amplifications par retries.

    FAQ

    1) Was Pentium 4 actually “bad,” or just misunderstood?

    C’était un pari étroit. Dans les charges qui correspondaient à ses forces (streaming, code prévisible, fort levier de fréquence),
    il pouvait bien performer. Dans des charges serveurs mixtes, il livrait souvent une performance réelle par watt et par dollar pire que les alternatives.
    « Mal compris » est généreux ; « mal vendu » est plus proche.

    2) Why did higher GHz not translate into higher performance?

    Parce que la performance dépend du travail utile par cycle (IPC) et de la fréquence des stalls mémoire, branches et contentions.
    NetBurst augmentait le nombre de cycles mais réduisait souvent le travail utile par cycle sous charges réelles.

    3) What’s the operational lesson for modern systems?

    N’acceptez pas une métrique d’accroche unique. Pour les CPU c’est les GHz ; pour le stockage c’est les « IOPS » ; pour les réseaux c’est les « Gbps ».
    Demandez toujours : sous quelle latence, avec quelle concurrence, et quel comportement de queue ?

    4) Did Hyper-Threading “fix” NetBurst?

    Cela a aidé le débit dans certains cas en remplissant des créneaux d’exécution inoccupés, mais cela n’a pas changé les fondamentaux :
    pénalités de pipeline profond, goulots mémoire, et contraintes thermiques. Cela pouvait aussi aggraver la latence de queue en ajoutant de la contention.
    Traitez-le comme un réglage, pas comme un bien par défaut.

    5) Why did Pentium M sometimes beat Pentium 4 at much lower clocks?

    Pentium M (de la lignée P6) mettait l’accent sur l’IPC et l’efficacité. Dans des charges branchées et sensibles au cache, un IPC plus élevé
    plus une meilleure efficacité surpassent souvent une fréquence brute, surtout quand la fréquence provoque du throttling thermique.

    6) How can I tell if my workload is memory-bound instead of CPU-bound?

    Recherchez un IPC bas avec beaucoup de défauts de cache dans perf, plus une amélioration limitée quand vous ajoutez des cœurs ou augmentez la fréquence.
    Vous verrez aussi le débit plafonner pendant que le CPU reste « occupé ». C’est généralement un mur mémoire ou de contention.

    7) Is thermal throttling really common enough to matter?

    Sur des designs qui chauffent et dans des datacenters réels, oui. Même un throttling modeste crée du jitter. Le jitter devient latence de queue,
    et la latence de queue devient incidents quand retries et timeouts amplifient la charge.

    8) What should I benchmark to avoid GHz-era mistakes?

    Benchmarchez le service réel : mix de requêtes réaliste, taille de dataset réaliste, concurrence réaliste, et reportez p95/p99 + débit.
    Ajoutez une phase cache-froid et un run soutenu assez long pour chauffer le système.

    9) Are there modern equivalents of the NetBurst trap?

    Oui. Chaque fois que vous optimisez une métrique de pic au détriment du comportement systémique : fréquences turbo sans budget thermique,
    benchmarks de stockage qui ignorent la latence fsync, ou tests de débit réseau qui ignorent la perte de paquets sous charge. Le schéma est le même :
    le pic gagne la diapositive, la queue perd le client.

    Conclusion : que faire la prochaine fois qu’on vous vend des GHz

    NetBurst n’est pas juste une anecdote CPU rétro. C’est une histoire claire sur les incitations, la mesure, et le coût de parier sur un seul nombre.
    Intel a optimisé pour la fréquence parce que le marché payait pour la fréquence. Les charges qui importaient —
    code branché, systèmes mémoire-intensifs, racks contraints thermiquement — ont envoyé la facture.

    Les étapes pratiques suivantes sont ennuyeuses, et c’est pourquoi elles fonctionnent :

    1. Définir la performance par la latence de queue, pas par le débit de pic et surtout pas par la vitesse d’horloge.
    2. Instrumenter pour les goulots : compteurs perf, PSI, métriques de paging, et signaux thermiques/throttling.
    3. Benchmarchez comme en production : concurrence, taille des données, comportement cache, soak thermique, et mix de requêtes réaliste.
    4. Traitez les thermiques comme de la capacité : si le CPU throttle, votre architecture est « limitée par le refroidissement ». Admettez-le.
    5. Soyez méfiant des « performances gratuites » : HT/SMT, concurrence agressive, et micro-optimisations qui ignorent la contention.

    Si vous ne retenez qu’une chose : les horloges sont un composant, pas une garantie. Le système est le produit. Exploitez-le comme tel.