Le Dilemme NUMA
Avant de plonger dans les détails du réglage du planificateur, posons le décor. Les architectures à accès mémoire non uniforme (NUMA) sont devenues la norme dans le matériel serveur moderne. Mais voici le hic : beaucoup d'entre nous continuent de développer et de déployer nos microservices Go comme si nous travaillions avec un accès mémoire uniforme. C'est comme essayer de faire entrer un piquet carré dans un trou rond – ça peut fonctionner, mais ce n'est pas optimal.
Pourquoi NUMA est important pour les microservices Go
Le runtime de Go est assez intelligent, mais il n'est pas omniscient. En ce qui concerne la prise en compte de NUMA, il a besoin d'un peu d'aide de notre part. Voici pourquoi la prise en compte de NUMA est cruciale pour vos microservices Go :
- La latence d'accès à la mémoire peut varier considérablement entre les nœuds NUMA locaux et distants
- Un placement incorrect des threads et de la mémoire peut entraîner une dégradation des performances
- Les performances du ramasse-miettes de Go peuvent être affectées par les effets NUMA
Ignorer NUMA dans vos microservices Go, c'est comme ignorer l'existence du trafic lors de la planification d'un voyage en voiture. Bien sûr, vous pourriez atteindre votre destination, mais le voyage sera loin d'être fluide.
Entrée du Planificateur Complètement Équitable (CFS)
Parlons maintenant de notre personnage principal : le Planificateur Complètement Équitable. Malgré son nom, le CFS n'est pas toujours complètement équitable lorsqu'il s'agit de systèmes NUMA. Mais avec un peu de réglage, nous pouvons le faire fonctionner à merveille pour nos microservices Go.
CFS : Le Bon, le Mauvais et le Laid NUMA
Le CFS est conçu pour être, eh bien, équitable. Il essaie de donner à chaque processus une part égale du temps CPU. Mais dans un monde NUMA, l'équité n'est pas toujours ce que nous voulons. Parfois, nous devons être un peu injustes pour atteindre des performances optimales. Voici un aperçu rapide :
- Le Bon : Le CFS offre une bonne réactivité globale du système et une équité
- Le Mauvais : Il peut entraîner des migrations de tâches inutiles entre les nœuds NUMA
- Le Laid NUMA : Sans réglage approprié, il peut causer une latence accrue d'accès à la mémoire pour les microservices Go
Réglage du CFS pour les Microservices Go Sensibles à NUMA
Bien, il est temps de retrousser nos manches et de nous salir les mains avec quelques réglages de planificateur. Voici les principaux domaines sur lesquels nous allons nous concentrer :
1. Ajustement des Domaines de Planification
Les domaines de planification définissent comment le planificateur voit la topologie du système. En les ajustant, nous pouvons rendre le CFS plus conscient de NUMA :
# Vérifier les domaines de planification actuels
cat /proc/sys/kernel/sched_domain/cpu0/domain*/name
# Ajuster les paramètres du domaine de planification
echo 1 > /proc/sys/kernel/sched_domain/cpu0/domain0/prefer_local_spreading
Cela indique au planificateur de préférer garder les tâches sur le même nœud NUMA lorsque c'est possible, réduisant ainsi les migrations inutiles.
2. Réglage Fin de sched_migration_cost_ns
Ce paramètre contrôle l'empressement du planificateur à migrer les tâches entre les CPU. Pour les systèmes NUMA exécutant des microservices Go, nous voulons souvent augmenter cette valeur :
# Vérifier la valeur actuelle
cat /proc/sys/kernel/sched_migration_cost_ns
# Augmenter la valeur (par exemple, à 1000000 nanosecondes)
echo 1000000 > /proc/sys/kernel/sched_migration_cost_ns
Ce changement rend le planificateur moins susceptible de déplacer des tâches entre les nœuds NUMA, réduisant ainsi les chances d'accès à la mémoire distante.
3. Utilisation des cgroups pour une Allocation de Ressources Sensible à NUMA
Les groupes de contrôle (cgroups) peuvent être un outil puissant pour imposer une allocation de ressources sensible à NUMA. Voici un exemple simple de comment utiliser les cgroups pour épingler un microservice Go à un nœud NUMA spécifique :
# Créer un cgroup pour notre microservice Go
mkdir /sys/fs/cgroup/cpuset/go_microservice
# Assigner les CPU et les nœuds mémoire
echo "0-3" > /sys/fs/cgroup/cpuset/go_microservice/cpuset.cpus
echo "0" > /sys/fs/cgroup/cpuset/go_microservice/cpuset.mems
# Exécuter le microservice Go dans ce cgroup
cgexec -g cpuset:go_microservice ./my_go_microservice
Cela garantit que notre microservice Go n'utilise que les CPU et la mémoire d'un seul nœud NUMA, réduisant ainsi l'accès à la mémoire inter-nœuds.
Le Runtime Go : Votre Allié Sensible à NUMA
Bien que nous nous concentrions sur le réglage du planificateur, n'oublions pas que le runtime de Go peut être notre allié dans la quête de la prise en compte de NUMA. Voici quelques conseils spécifiques à Go :
1. GOGC et NUMA
La variable d'environnement GOGC contrôle le comportement du ramasse-miettes de Go. Dans les systèmes NUMA, vous pourriez vouloir ajuster cette valeur pour réduire la fréquence des collectes globales :
export GOGC=200
Cela indique au runtime Go de déclencher la collecte des ordures moins fréquemment, réduisant potentiellement l'accès à la mémoire inter-nœuds pendant la collecte.
2. Utilisation de runtime.NumCPU()
Lorsque vous écrivez du code Go pour les systèmes NUMA, soyez attentif à la façon dont vous utilisez les goroutines. Voici un exemple simple de comment créer un pool de travailleurs sensible à NUMA :
import "runtime"
func createNUMAAwareWorkerPool() {
numCPU := runtime.NumCPU()
for i := 0; i < numCPU; i++ {
go worker(i)
}
}
func worker(id int) {
runtime.LockOSThread()
// Logique du travailleur ici
}
En utilisant runtime.NumCPU()
et runtime.LockOSThread()
, nous créons un pool de travailleurs qui est plus susceptible de respecter les limites NUMA.
Mesurer l'Impact
Tout ce réglage est génial, mais comment savoir si cela fait vraiment une différence ? Voici quelques outils et métriques à surveiller :
- numastat : Fournit des statistiques de mémoire NUMA
- perf : Peut être utilisé pour mesurer les ratés de cache et les modèles d'accès à la mémoire
- Profilage intégré de Go : Utilisez
runtime/pprof
pour profiler votre application avant et après le réglage
Voici un exemple rapide de comment utiliser numastat
pour vérifier l'utilisation de la mémoire NUMA :
numastat -p $(pgrep my_go_microservice)
Recherchez les déséquilibres dans l'allocation de mémoire entre les nœuds NUMA. Si vous voyez beaucoup d'accès à la mémoire "étrangère", votre réglage pourrait nécessiter quelques ajustements.
Pièges et Surprises
Avant de vous lancer et de commencer à régler chaque système en vue, un mot de prudence :
- Un réglage excessif peut entraîner une sous-utilisation des ressources
- Ce qui fonctionne pour un microservice Go peut ne pas fonctionner pour un autre
- Le réglage du planificateur peut interagir de manière complexe avec le comportement du runtime de Go
Mesurez toujours, testez et validez vos changements dans un environnement contrôlé avant de les déployer en production. Rappelez-vous, avec un grand pouvoir vient une grande responsabilité (et potentiellement de gros maux de tête si vous n'êtes pas prudent).
Conclusion : L'Art de l'Équilibre
Régler le Planificateur Complètement Équitable pour les microservices Go sensibles à NUMA est véritablement un art. Il s'agit de trouver le bon équilibre entre équité, performance et utilisation des ressources. Voici les points clés à retenir :
- Comprenez votre matériel : l'architecture NUMA est importante
- Réglez les paramètres du CFS en tenant compte de NUMA
- Utilisez les cgroups pour un contrôle précis
- Travaillez avec le runtime de Go, pas contre lui
- Mesurez et validez toujours vos efforts de réglage
Rappelez-vous, le but n'est pas de créer un système parfaitement conscient de NUMA (ce qui est pratiquement impossible), mais de trouver le point idéal où vos microservices Go fonctionnent au mieux dans les contraintes de votre architecture NUMA.
Alors, la prochaine fois que quelqu'un dit, "Ce n'est qu'un planificateur, à quel point cela pourrait-il être complexe ?" vous pouvez sourire en connaissance de cause et leur montrer cet article. Bon réglage, et que vos microservices Go fonctionnent toujours sans accroc à travers les nœuds NUMA !
"Dans le monde des microservices Go sensibles à NUMA, le planificateur n'est pas juste un arbitre – c'est le chorégraphe d'une danse complexe entre le code et le matériel."
Avez-vous des histoires de guerre sur le réglage du planificateur pour les systèmes NUMA ? Ou peut-être quelques astuces Go astucieuses pour la prise en compte de NUMA ? Partagez-les dans les commentaires ci-dessous. Apprenons des triomphes (et des catastrophes) des uns et des autres !