Het NUMA Raadsel

Voordat we ons verdiepen in de fijne kneepjes van het afstemmen van de scheduler, laten we de basis leggen. Non-Uniform Memory Access (NUMA) architecturen zijn de norm geworden in moderne serverhardware. Maar hier is het probleem: velen van ons ontwikkelen en implementeren onze Go-microservices nog steeds alsof we werken met uniforme geheugentoegang. Het is alsof je een vierkante pen in een rond gat probeert te passen – het kan werken, maar het is verre van optimaal.

Waarom NUMA Belangrijk is voor Go Microservices

De runtime van Go is behoorlijk slim, maar niet alwetend. Als het gaat om NUMA-bewustzijn, heeft het een beetje hulp van ons stervelingen nodig. Hier is waarom NUMA-bewustzijn cruciaal is voor je Go-microservices:

  • De latentie van geheugentoegang kan aanzienlijk variëren tussen lokale en externe NUMA-nodes
  • Onjuiste plaatsing van threads en geheugen kan leiden tot prestatievermindering
  • De prestaties van de garbage collector van Go kunnen worden beïnvloed door NUMA-effecten

NUMA negeren in je Go-microservices is als het negeren van het bestaan van verkeer bij het plannen van een roadtrip. Natuurlijk, je kunt je bestemming bereiken, maar de reis zal verre van soepel zijn.

De Completely Fair Scheduler (CFS)

Laten we het nu hebben over onze hoofdrolspeler: de Completely Fair Scheduler. Ondanks zijn naam is CFS niet altijd volledig eerlijk als het gaat om NUMA-systemen. Maar met een beetje afstemming kunnen we het wonderen laten verrichten voor onze Go-microservices.

CFS: Het Goede, Het Slechte en Het NUMA-Lelijke

CFS is ontworpen om eerlijk te zijn. Het probeert elk proces een gelijke hoeveelheid CPU-tijd te geven. Maar in een NUMA-wereld is eerlijkheid niet altijd wat we willen. Soms moeten we een beetje oneerlijk zijn om optimale prestaties te bereiken. Hier is een kort overzicht:

  • Het Goede: CFS biedt goede algehele systeemresponsiviteit en eerlijkheid
  • Het Slechte: Het kan leiden tot onnodige taakmigraties tussen NUMA-nodes
  • Het NUMA-Lelijke: Zonder juiste afstemming kan het verhoogde geheugentoegangslatentie veroorzaken voor Go-microservices

CFS Afstemmen voor NUMA-Bewuste Go Microservices

Goed, tijd om de mouwen op te stropen en aan de slag te gaan met het afstemmen van de scheduler. Hier zijn de belangrijkste gebieden waarop we ons zullen concentreren:

1. Aanpassen van Scheduling Domains

Scheduling domains bepalen hoe de scheduler de systeemtopologie bekijkt. Door deze aan te passen, kunnen we CFS meer NUMA-bewust maken:


# Controleer huidige scheduling domains
cat /proc/sys/kernel/sched_domain/cpu0/domain*/name

# Pas scheduling domain parameters aan
echo 1 > /proc/sys/kernel/sched_domain/cpu0/domain0/prefer_local_spreading

Dit vertelt de scheduler om taken bij voorkeur op dezelfde NUMA-node te houden wanneer mogelijk, waardoor onnodige migraties worden verminderd.

2. Fijn Afstemmen van sched_migration_cost_ns

Deze parameter bepaalt hoe graag de scheduler taken tussen CPU's migreert. Voor NUMA-systemen die Go-microservices draaien, willen we deze waarde vaak verhogen:


# Controleer huidige waarde
cat /proc/sys/kernel/sched_migration_cost_ns

# Verhoog de waarde (bijv. naar 1000000 nanoseconden)
echo 1000000 > /proc/sys/kernel/sched_migration_cost_ns

Deze wijziging maakt de scheduler minder geneigd om taken tussen NUMA-nodes te verplaatsen, waardoor de kans op externe geheugentoegang wordt verkleind.

3. Gebruikmaken van cgroups voor NUMA-Bewuste Resource Allocatie

Control groups (cgroups) kunnen een krachtig hulpmiddel zijn voor het afdwingen van NUMA-bewuste resource allocatie. Hier is een eenvoudig voorbeeld van hoe je cgroups kunt gebruiken om een Go-microservice aan een specifieke NUMA-node toe te wijzen:


# Maak een cgroup voor onze Go-microservice
mkdir /sys/fs/cgroup/cpuset/go_microservice

# Wijs CPU's en geheugennodes toe
echo "0-3" > /sys/fs/cgroup/cpuset/go_microservice/cpuset.cpus
echo "0" > /sys/fs/cgroup/cpuset/go_microservice/cpuset.mems

# Voer de Go-microservice uit binnen deze cgroup
cgexec -g cpuset:go_microservice ./my_go_microservice

Dit zorgt ervoor dat onze Go-microservice alleen CPU's en geheugen van een enkele NUMA-node gebruikt, waardoor cross-node geheugentoegang wordt verminderd.

De Go Runtime: Je NUMA-Bewuste Bondgenoot

Terwijl we ons richten op het afstemmen van de scheduler, laten we niet vergeten dat de runtime van Go onze bondgenoot kan zijn in de zoektocht naar NUMA-bewustzijn. Hier zijn een paar Go-specifieke tips:

1. GOGC en NUMA

De GOGC-omgevingsvariabele regelt het gedrag van de garbage collector van Go. In NUMA-systemen wil je deze waarde misschien aanpassen om de frequentie van globale collecties te verminderen:


export GOGC=200

Dit vertelt de Go-runtime om minder vaak garbage collection uit te voeren, wat mogelijk cross-node geheugentoegang tijdens de collectie vermindert.

2. Gebruikmaken van runtime.NumCPU()

Bij het schrijven van Go-code voor NUMA-systemen, let op hoe je goroutines gebruikt. Hier is een eenvoudig voorbeeld van hoe je een NUMA-bewuste worker pool kunt maken:


import "runtime"

func createNUMAAwareWorkerPool() {
    numCPU := runtime.NumCPU()
    for i := 0; i < numCPU; i++ {
        go worker(i)
    }
}

func worker(id int) {
    runtime.LockOSThread()
    // Worker logica hier
}

Door gebruik te maken van runtime.NumCPU() en runtime.LockOSThread(), creëren we een worker pool die meer geneigd is om NUMA-grenzen te respecteren.

De Impact Meten

Al deze afstemming is geweldig, maar hoe weten we of het daadwerkelijk een verschil maakt? Hier zijn enkele tools en statistieken om in de gaten te houden:

  • numastat: Biedt NUMA-geheugenstatistieken
  • perf: Kan worden gebruikt om cache-missers en geheugentoegangspatronen te meten
  • Go's ingebouwde profilering: Gebruik runtime/pprof om je applicatie voor en na het afstemmen te profileren

Hier is een snel voorbeeld van hoe je numastat kunt gebruiken om NUMA-geheugengebruik te controleren:


numastat -p $(pgrep my_go_microservice)

Zoek naar onevenwichtigheden in geheugentoewijzing over NUMA-nodes. Als je veel "vreemde" geheugentoegang ziet, moet je afstemming mogelijk worden aangepast.

Valkuilen en Verrassingen

Voordat je begint met het afstemmen van elk systeem dat je tegenkomt, een waarschuwing:

  • Overmatig afstemmen kan leiden tot onderbenutting van resources
  • Wat werkt voor de ene Go-microservice, werkt mogelijk niet voor een andere
  • Het afstemmen van de scheduler kan op complexe manieren interageren met het runtime-gedrag van Go

Meet, test en valideer altijd je wijzigingen in een gecontroleerde omgeving voordat je naar productie gaat. Onthoud, met grote kracht komt grote verantwoordelijkheid (en mogelijk grote hoofdpijn als je niet voorzichtig bent).

Afronden: De Kunst van Balans

Het afstemmen van de Completely Fair Scheduler voor NUMA-bewuste Go-microservices is echt een kunstvorm. Het gaat om het vinden van de juiste balans tussen eerlijkheid, prestaties en resourcegebruik. Hier zijn de belangrijkste punten:

  • Begrijp je hardware: NUMA-architectuur is belangrijk
  • Stem CFS-parameters af met NUMA in gedachten
  • Maak gebruik van cgroups voor gedetailleerde controle
  • Werk samen met de runtime van Go, niet ertegen
  • Meet en valideer altijd je afstemmingsinspanningen

Onthoud, het doel is niet om een perfect NUMA-bewust systeem te creëren (wat praktisch onmogelijk is), maar om het punt te vinden waar je Go-microservices op hun best presteren binnen de beperkingen van je NUMA-architectuur.

Dus, de volgende keer dat iemand zegt: "Het is maar een scheduler, hoe complex kan het zijn?" kun je glimlachen en ze naar dit artikel verwijzen. Veel succes met afstemmen, en moge je Go-microservices altijd soepel draaien over NUMA-nodes!

"In de wereld van NUMA-bewuste Go-microservices is de scheduler niet alleen een scheidsrechter – het is de choreograaf van een complexe dans tussen code en hardware."

Heb je verhalen over het afstemmen van de scheduler voor NUMA-systemen? Of misschien enkele slimme Go-trucs voor NUMA-bewustzijn? Laat ze achter in de reacties hieronder. Laten we leren van elkaars triomfen (en catastrofes)!