Cessez de laisser l’ordonnanceur taxer votre base de données : affinité CPU optimisée pour le cache pour Postgres et Val

Par Diogo Hudson Dias
Site reliability engineer reviewing CPU topology and cache performance graphs on dual monitors in a São Paulo office.

Votre base de données n’est pas lente. Votre topologie CPU vous facture une taxe que vous n’avez jamais approuvée. Sur des serveurs modernes — en particulier AMD EPYC avec plusieurs core complexes — laisser Linux planifier Postgres ou Valkey sur toute la machine détruit souvent la localité du cache L3 et la latence p95. Le correctif, ce n’est pas une instance plus grosse. C’est d’aligner vos processus avec le silicium que vous payez déjà.

Des benchmarks publics récents et des retours de la communauté montrent des gains à deux chiffres en gardant les workers de base de données à l’intérieur d’un même domaine de cache (CCD/CCX chez AMD, frontières de nœuds NUMA de façon générale). Nous observons les mêmes gains sur le terrain : 15–35 % de débit en plus et une p95 resserrée simplement en étant délibéré sur le placement CPU et mémoire. Pas de changement de code. Pas de nouveau matériel. Juste traiter le cache comme une ressource de première classe.

Pourquoi cela compte maintenant

Deux réalités 2026 se télescopent :

  • Le nombre de cœurs a dépassé la capacité des applications à en tirer parti. Une machine à 64–128 vCPU paraît efficace sur une facture cloud mais masque plusieurs caches de dernier niveau et contrôleurs mémoire. Répartir aveuglément des threads chauds à travers eux est de l’auto-sabotage.
  • Les bases de données sont sensibles à la latence et adorent le cache. Postgres et Valkey réutilisent à l’extrême des pages et métadonnées « chaudes ». Affamez leur localité et vous multipliez les misses à chaque chemin d’exécution.

Si vous opérez dans des régions plus chères (par exemple, AWS sa-east-1 au Brazil affiche souvent une prime à deux chiffres par rapport à us-east-1), extraire 20 % de plus par nœud n’est pas une micro‑optimisation — c’est du pilotage budgétaire.

Topologie CPU moderne en 90 secondes

  • AMD EPYC (Zen 3/4/5) : les cœurs sont regroupés en Core Complex Dies (CCD). Chaque CCD possède son propre cache L3. Traverser les limites de CCD signifie plus de latence et davantage de misses L3. Beaucoup d’instances à haut nombre de vCPU sont plusieurs CCD assemblés.
  • Intel Xeon (Skylake et suivants) : un interconnect en maillage avec un LLC partagé mais partitionné. La localité reste importante ; déplacer des données au travers du maillage coûte des cycles.
  • Arm (AWS Graviton) : généralement un seul domaine NUMA avec des L2/L3 généreux, ce qui réduit mais n’élimine pas les préoccupations de localité, surtout avec un grand nombre de threads.

Idée clé : gardez les threads qui coopèrent (ceux qui touchent les mêmes données) près du même cache de dernier niveau et du contrôleur mémoire qui l’alimente. Cela signifie moins de misses de cache, moins de trajets inter‑dies, un meilleur IPC (Instructions Per Cycle) et une latence de queue plus heureuse.

Pourquoi Postgres et Valkey sont particulièrement sensibles

Postgres

  • Processus par connexion : chaque backend secoue ses propres piles plus le buffer partagé qui mappe les mêmes tables et index.
  • Chemins critiques : recherches btree, vérifications de visibility map, accès au catalogue — tout cela aime le cache. Dispersez‑les à travers des CCD et ces pages chaudes ne cessent d’être évincées.
  • Processus auxiliaires : walwriter, checkpointer, background writer et autovacuum se disputent CPU et bande passante mémoire. Regrouper backends et mémoire partagée sur le même nœud NUMA améliore la cohérence.

Valkey (Redis)

  • QPS élevé, petits objets : la charge est « tiny‑hot », exactement ce que le L3 accélère.
  • E/S multithreadées : les builds modernes utilisent des threads d’E/S ; un mauvais placement provoque des rebonds inter‑cœurs et du thrash de socket.
  • Shards/instances : exécuter plusieurs shards sur un gros hôte est courant. Si ces shards n’honorent pas les domaines de cache, vous éliminez l’avantage du sharding.

Faut‑il le faire ? Une grille de décision rapide

Dites oui à un projet « cache‑aware » si au moins deux éléments sont vrais :

  • Le CPU de votre base dépasse fréquemment 50 % tandis que la p95 grimpe sous charge.
  • perf stat montre un taux de cache‑miss élevé (>10–15 %) et un IPC qui s’affaisse (<1) aux heures de pointe.
  • Le jeu de travail tient en RAM et le stockage n’est pas votre goulot (la p95 de latence disque est correcte).
  • Vous êtes sur des instances avec 32+ vCPU ou des topologies multi‑CCD/NUMA connues (courant sur c6a/c7a, c3d, Dav5/Dv5, etc.).

Si le stockage est votre goulot, corrigez‑le d’abord. Si vous êtes majoritairement au repos, vous ne verrez pas les gains. Cela paye quand vous poussez réellement la machine.

Voie rapide : plan de déploiement en 10 jours

Jours 1–2 : audit de topologie

  • Cartographiez le CPU et les caches : exécutez lscpu -e et numactl -H. Sur AMD, cherchez des indices CCD/CCX ; des outils comme hwloc dessinent cela joliment.
  • Capturez une perf de base : perf stat -e cycles,instructions,cache-misses pendant le pic ; journalisez la p95/p99 Postgres et la latence/QPS Valkey.
  • Confirmez que le stockage n’est pas le coupable : utilisez iostat et des outils ebpf (par ex. perf-tools cachestat, biolatency) pour vérifier.

Jours 3–4 : benchmarks de référence

  • Postgres : pgbench avec un dimensionnement réaliste (pensez facteur d’échelle égal aux connexions actives, pas des valeurs par défaut jouet). Mesurez TPS et p95.
  • Valkey : redis-benchmark ou valkey-benchmark avec vos tailles de clés et profondeur de pipeline.

Jours 5–6 : prototype de pinning en préproduction

  • Créez un cpuset pour la base de données qui correspond à un domaine de cache (par exemple, les cœurs 0–15 qui partagent le L3). Gardez un ensemble « housekeeping » séparé pour le noyau, les interruptions NIC et les tâches d’arrière‑plan.
  • Liez la mémoire localement : lancez le service avec numactl --cpunodebind=X --membind=X pour que la mémoire partagée privilégie le même nœud.
  • Désactivez irqbalance pour le test et définissez manuellement les affinités IRQ de la NIC sur les cœurs housekeeping ; définissez rps_cpus de la même manière.

Jour 7 : canari en production

  • Basculer un sous‑ensemble de trafic vers la/les instance(s) épinglée(s). Comparez débit et p95/p99. Visez 15–35 % de TPS en plus ou une p95 nettement resserrée.
  • Guettez les régressions : retard d’autovacuum, pics de checkpoint, ou pertes de paquets causées par des interruptions mal épinglées.

Jours 8–9 : industrialisation

  • Systemd : ajoutez un drop‑in pour Postgres avec AllowedCPUs= et MemoryNUMAPolicy=local. Rendez persistants les masques IRQ dans les scripts de boot.
  • Kubernetes : activez la politique statique de CPU Manager, exécutez le pod DB en Guaranteed (requests=limits) et demandez des hugepages si pertinent. Utilisez Topology Manager pour aligner CPU et mémoire. Gardez des « reserved CPUs » pour l’OS séparés.

Jour 10 : documentation et tableaux de bord

  • Épinglez la carte de topologie, les masques de cpuset et les affectations d’IRQ dans vos runbooks.
  • Ajoutez des panneaux Grafana pour les cache misses, l’IPC, la latence de run queue et la p95 DB. Si ce n’est pas sur un graphe, cela n’est pas arrivé.

Guide concret : bare metal ou VM

1) Identifier le domaine de cache à utiliser

  • Utilisez lscpu -e pour lister les colonnes CPU, socket, node et L3 ID. Choisissez un ensemble contigu qui partage le même L3 ID.
  • Confirmez avec hwloc ou en inspectant /sys/devices/system/cpu/cpuN/cache/index3/id.

2) Construire les cpusets

  • Créez deux cgroups : db et housekeeping. Assignez le service db aux CPUs locaux au L3 et tout le reste (irq, systemd-oomd, journald) à l’ensemble housekeeping.
  • Dans systemd, utilisez AllowedCPUs= pour le service et CPUAffinity= pour les services OS que vous voulez éloigner des cœurs chauds.

3) Lier la mémoire et activer les huge pages

  • Postgres : définissez huge_pages = on (assurez‑vous que vm.nr_hugepages est provisionné). Lancez sous numactl --membind pour garder le buffer pool local.
  • Valkey : alignez les processus de shard sur leur cpuset et utilisez SO_REUSEPORT avec un listener par shard si vous exposez un VIP unique en frontal.

4) IRQ et réseau

  • Désactivez ou restreignez irqbalance. Définissez manuellement les IRQ de la NIC sur les cœurs housekeeping (vérifiez /proc/interrupts, définissez /proc/irq/*/smp_affinity_list).
  • Pour des taux de paquets élevés, définissez rps_cpus et xps_cpus des files NIC pour correspondre à l’ensemble housekeeping afin d’éviter les pollueurs sur les cœurs DB.

5) Réglages Postgres qui en profitent

  • shared_buffers : 25–40 % de la RAM est une plage raisonnable pour beaucoup de charges OLTP. Plus gros n’est pas toujours mieux ; la localité compte davantage que la taille brute.
  • max_parallel_workers et max_worker_processes : gardez le total de workers actifs dans la limite de vos cœurs épinglés. Dépasser le budget de cœurs locaux au L3 détruit le gain.
  • checkpoint_timeout, max_wal_size, autovacuum_work_mem : étalez les travaux lourds pour éviter les à‑coups synchronisés qui pénalisent le cache.

Variante Kubernetes (ce que la plupart d’entre vous exécutent)

  • Utilisez la QoS Guaranteed : les requests doivent égaler les limits pour CPU et mémoire. Sinon, le kubelet peut vous découper dans le temps à travers des CPUs et ruiner la localité.
  • Activez la politique statique de CPU Manager : elle donne des CPUs exclusifs aux pods Guaranteed.
  • Topology Manager : réglez‑le sur restricted ou best‑effort afin que CPU et mémoire atterrissent sur le même nœud NUMA.
  • Réservez des CPUs pour l’OS et les démons système : définissez --reserved-cpus sur le kubelet pour que la plomberie du nœud n’empiète pas sur le domaine de cache de votre DB.
  • Huge pages : demandez hugepages-2Mi dans la spec du Pod ; configurez‑les d’abord au niveau du nœud.
  • Anti‑affinité et sélecteurs de nœud : éloignez les pods DB des voisins bruyants et assurez‑vous qu’ils se planifient sur le type d’instance/topologie visé.

Si vous utilisez un operator Postgres (Crunchy, Zalando), vous pouvez toujours employer ces primitives : définissez requests=limits, ajoutez des node selectors/taints et assurez‑vous que l’operator ne vous replanifie pas sur un matériel inadéquat.

À propos des instances cloud : points d’attention

  • AWS AMD (c6a/c7a/m7a/r7a) : de nombreuses tailles assemblent plusieurs CCD. Préférez moins de grosses machines seulement si vous vous engagez à faire du pinning ; sinon, des instances de taille moyenne peuvent gagner grâce à une topologie plus simple.
  • AWS Graviton (c7g/m7g) : les bénéfices de localité sont plus faibles mais bien réels sous fort multithreading. Les gains se voient surtout sur la stabilité p95 plus que sur le pic de TPS.
  • GCP C3D (AMD) / C3 (Intel) : même conseil : cartographiez les domaines de cache avant de faire évoluer une flotte qui se tire dans les pattes.
  • Azure Dav5/Dv5 : semblable aux familles AMD/Intel sur AWS ; la carte NUMA est votre amie. N’ordonnancez pas à l’aveugle.

Quels gains sont réalistes ?

Des retours publics de praticiens montrent :

  • Postgres : 15–25 % de TPS en plus sur un OLTP type pgbench en épinglant à l’équivalent d’un seul CCD de cœurs avec mémoire locale, versus laisser les backends errer sur deux CCD ou plus. La p95 se resserre souvent dans des proportions similaires grâce à moins d’excursions inter‑dies.
  • Valkey : 20–35 % de QPS en plus en exécutant plusieurs shards, chacun épinglé à son propre domaine de cache, et des threads d’E/S alignés. Les pipelines à petits payloads en profitent le plus.

Votre kilométrage variera, mais si vous ne voyez pas de gains à deux chiffres, revérifiez votre mapping CPU et le placement des IRQ ; une seule interruption égarée sur un cœur chaud peut annuler le bénéfice.

Compromis et écueils (à lire deux fois)

  • Plafond vs. efficacité : se limiter à un seul domaine de cache peut plafonner le débit absolu si votre charge se scale vraiment linéairement avec plus de cœurs. Beaucoup de charges OLTP cessent d’évoluer passé un point ; elles déraillent sous les tempêtes de cohérence — le pinning aide ici.
  • Complexité opérationnelle : vous gérez désormais du pinning et des IRQ. Modélisez‑le dans Ansible/Terraform ou votre couche de cluster pour survivre aux redémarrages et rotations de nœuds.
  • L’orchestration de conteneurs se rebiffe : sans CPU Manager en statique et QoS Guaranteed, K8s vous tranche dans le temps vers la médiocrité. N’implémentez pas à moitié.
  • Bruit de l’hyperviseur : sur des hôtes mutualisés, vous ne contrôlez pas totalement le placement. Préférez des flavors d’hôtes dédiés si vous chassez chaque microseconde.
  • Thermique et boost : des cœurs épinglés et chauds tiennent des températures soutenues plus élevées ; surveillez l’étranglement. N’assumez pas que les fréquences boost vous sauveront.

Vérification : comment savoir que vous avez réellement gagné

  • Micro‑métriques : l’IPC doit monter ; le taux de cache‑miss doit baisser. Comparez perf stat à charge offerte égale.
  • SLO applicatifs : les latences p95/p99 doivent se resserrer et dériver moins lors des montées de trafic.
  • Bruit système : la latence de file d’attente d’exécution sur les cœurs chauds doit s’aplatir ; moins de commutations de contexte involontaires ; les IRQ de la NIC ne doivent plus apparaître sur les cœurs DB dans /proc/interrupts.
  • Coût par TPS : suivez les TPS par dollar‑heure. Si vous pouvez réduire une classe d’instance ou consolider des nœuds après tuning, c’est du cash en main.

Rendre cela reproductible

  • La topologie comme du code : stockez les masques de cpuset et les node selectors avec votre code infra. Pour K8s, conditionnez les déploiements sur des labels de nœud annonçant la bonne topologie (Node Feature Discovery peut aider).
  • Images de référence : bakez les configs d’IRQ, paramètres hugepages et drop‑ins systemd dans vos AMI/images. Ne comptez pas sur des scripts shell ad hoc.
  • Observabilité intégrée : les tableaux de bord de métriques de cache font partie de la fonctionnalité. Alertez sur les explosions de cache‑miss et la runqlat en hausse sur les cœurs épinglés.

Où le nearshore s’insère

C’est du travail parfait pour du nearshore : borné dans le temps, orienté infrastructure et mesurable. Une équipe avec une forte expertise Linux et bases de données peut livrer cela en deux semaines avec 6–8 heures de chevauchement pour piloter des canaris et itérer. Pour les équipes opérant sur AWS sa-east-1 au Brazil, un gain de 20 % de débit peut compenser immédiatement les primes régionales ; pour les équipes US, c’est une soupape pour retarder un sharding coûteux ou une réarchitecture.

Quand ne pas s’embêter

  • Votre charge est clairement contrainte par l’E/S (p95 de stockage médiocre) ou par le réseau (de tout petits paquets saturent la NIC avant le CPU).
  • Vous tournez déjà sur de petites VM à CCD/NUMA unique — il n’y a pas de topologie contre laquelle se battre.
  • Vous êtes planifié sur un hyperviseur bruyant et mutualisé et ne pouvez pas contrôler IRQ ni l’exclusivité CPU — fixez d’abord la question de la colocation.

L’essentiel

L’ordonnanceur par défaut de Linux est un miracle d’ingénierie généraliste. Votre base de données ne l’est pas. Traitez la localité de cache et NUMA comme une ligne budgétaire et vous cesserez de payer une taxe cachée à chaque pic de trafic. Ce n’est pas de la sorcellerie ; c’est de la discipline : mesurer, épingler, lier, vérifier. Faites‑le, et vous achèterez 15–35 % de marge pour des cacahuètes.

Points clés

  • Votre base peut payer une taxe cachée cache/NUMA — attendez‑vous à 15–35 % de gains via la localité.
  • Visez un seul domaine de cache (CCD/nœud NUMA) pour les workers Postgres/Valkey ; liez CPU et mémoire ensemble.
  • Utilisez cpusets/systemd sur VM ; CPU Manager en statique et QoS Guaranteed sur Kubernetes.
  • Fixez le placement des IRQ et du « housekeeping » ; une interruption égarée sur un cœur chaud peut effacer vos gains.
  • Vérifiez via IPC et metrics de cache‑miss plus p95/p99 ; bakez les réglages dans votre code infra.

Ready to scale your engineering team?

Tell us about your project and we'll get back to you within 24 hours.

Start a conversation