Linux NUMA Statistiken zeigen mir, wie gut Prozesse ihren Speicher lokal erhalten und wo entfernte Zugriffe Latenzen erhöhen. Ich erkläre, wie ich diese Zahlen zielgerichtet lese, Trends über Zeit bewerte und daraus klare Tuning-Schritte für Performance ableite.
Zentrale Punkte
- Zähler verstehen: numa_hit, numa_miss, numa_foreign, local_node, other_node, interleave_hit
- Kontext bewerten: Lastprofil, Topologie, Workload-Typ
- Trends messen: Vorher/Nachher und über Intervalle
- Prozesse prüfen: Systemweit vs. pro Prozess
- Tuning anwenden: Affinity, Policies, Platzierung
Was NUMA‑Statistiken wirklich zeigen
Ich lese NUMA‑Zahlen als Landkarte für Speicherlokalität und Datenwege. Ein hoher numa_hit bedeutet, dass Allokationen am gewünschten Knoten gelandet sind. Dagegen signalisiert numa_miss, dass der Kernel auf einen anderen Knoten ausweichen musste. Der Zähler numa_foreign zeigt das Gegenstück auf dem Zielknoten und ergänzt das Bild. Mit local_node und other_node erkenne ich, ob Zugriffe lokal blieben oder entfernten Speicher genutzt haben.
Diese Werte sind nie isoliert zu deuten, weil Workloads sehr unterschiedlich reagieren. Kurze Prozesse erzeugen vereinzelt Misses, ohne die Gesamtleistung spürbar zu verändern. Interleave-Policies wiederum erzeugen absichtlich verteilte Allokationen, was interleave_hit ansteigen lässt. Ich prüfe daher immer die beabsichtigte Policy und die aktuelle Last. Erst dann entscheide ich, ob ein Wert Handlungsbedarf auslöst oder zum Design passt.
Die Kernidee lautet für mich: Zähler liefern Signale, keine Urteile. Ich suche Muster über Zeit, nicht Einzelzahlen. So erkenne ich, ob eine Änderung am System die Lokalität verschiebt. Erst mit diesem Trendbild bewerte ich, ob ich Prozesse verschiebe, Policies anpasse oder CPU‑Bindungen setze. Jede NUMA‑Analyse startet daher mit einer klaren Fragestellung und wiederholbaren Messpunkten.
Kernzähler im Zusammenhang interpretieren
Ich vergleiche immer numa_hit und numa_miss miteinander, statt absolute Werte zu werten. Steigen Misses, prüfe ich parallel die Entwicklung von numa_foreign auf den potenziellen Zielknoten. Passen beide zusammen, zeigt das eine echte Verlagerung und kein reines Auslese-Artefakt. local_node und other_node ergänzen dieses Bild über die tatsächlichen Zugriffe. So erkenne ich, ob eine Allokation zwar lokal begann, aber der Betrieb später mehr entfernten Speicher las.
Ein einzelner hoher other_node stört mich nicht, wenn der Workload bewusst verteilt. Webserver mit vielen Workern profitieren dagegen von konsistenter Lokalität. Ich schaue mir deshalb Prozesse an, nicht nur die Gesamtsicht. Sobald einzelne Dienste aus der Reihe tanzen, greife ich an deren Platzierung an. Erst wenn systemweit Misses steigen, suche ich nach Topologie- oder Auslastungsgründen.
Vergleiche über Zeit: Messroutine, die trägt
Ich messe Zählerstände zu Beginn und Ende einer Lastphase und berechne die Differenz. Einzelwerte verwischen Effekte, Differenzen zeigen Bewegung. Wiederholte Intervalle von zum Beispiel 30 bis 60 Sekunden genügen oft, um Trends zu erkennen. Nach Deployments, Kernel‑Updates oder Hardware‑Änderungen vergleiche ich die gleichen Intervalle erneut. Fallen dann mehr Misses an oder verschiebt sich local_node, liegt eine echte Veränderung vor.
Solche Zeitreihen decken Platzierungsfehler schneller auf als Momentaufnahmen. Ich korreliere die Linien mit CPU‑Auslastung, Kontextwechseln und Speicherbelegung pro Knoten. Dadurch sehe ich, ob Engpässe im RAM eines Knotens zu Ausweichbewegungen führen. Oder ob neue Prozesse die Knotenverhältnisse kippen. Die reine Quote aus Hits und Misses erzähle ich immer als Verlauf, nicht als Einzelzahl.
Systemweit prüfen, dann Prozesse zoomen
Ich starte mit der Gesamtsicht aus numastat und prüfe erst danach einzelne Prozesse. Diese Reihenfolge spart Zeit, weil viele Effekte global sichtbar werden. Für die Prozesssicht nutze ich die prozessspezifische Ausgabe, um auffällige Dienste zu isolieren. Sobald Kandidaten feststehen, justiere ich die Platzierung über CPU‑ und Speicherbindung. Praktische Hinweise dazu bündelt der Beitrag zu CPU- und Memory-Affinity.
Gerade bei Java‑Diensten, PHP‑FPM oder Datenbanken genügt oft eine saubere Affinity, um Misses deutlich zu senken. Container‑Orchestrierung verschleiert diese Probleme gern, weil Scheduler ohne NUMA‑Bewusstsein verteilen. Ich kontrolliere daher die Knotenzuordnung pro Pod oder VM. Stimmen CPU‑Sets und RAM‑Zuweisung überein, steigt local_node messbar. Manche Probleme lösen sich, sobald der Prozess nahe zum benötigten Datensatz läuft.
NUMA‑Zähler im Überblick (Tabelle)
Die folgende Tabelle fasse ich ein, wenn ich ein neues System bewerte, damit ich jede Kennzahl rasch einordne. Sie zeigt Bedeutung, typische Interpretation und mögliche Maßnahmen. Ich lese sie nicht als starres Schema, sondern als Checkliste. Entscheidend bleibt der Abgleich mit Lastprofil und Server‑Topologie. Erst mit diesem Kontext treffe ich eine sinnvolle Entscheidung.
| Zähler | Bedeutung | Interpretation | Ansatz |
|---|---|---|---|
| numa_hit | Allokation am gewünschten Knoten | Hoher Wert ist positiv | Platzierung beibehalten |
| numa_miss | Allokation wich auf anderen Knoten aus | Erhöhtes Risiko für Latenzen | Affinity/Policy prüfen |
| numa_foreign | Fremde Allokation auf diesem Knoten | Gegenstück zu numa_miss | Zielknoten analysieren |
| local_node | Zugriffe auf lokalen Speicher | Höher ist günstiger | Prozess näher zum RAM |
| other_node | Zugriffe auf entfernten Speicher | Nur bedenklich ohne Absicht | Topologie/Last prüfen |
| interleave_hit | Treffer bei Interleave‑Verteilung | Erwartet bei Interleave‑Policy | Gleichmäßigkeit bewerten |
Mit dieser Übersicht entscheide ich schneller, wann ich eingreife. Ein Anstieg von numa_miss ohne erklärbare Änderung triggert eine Ursachenanalyse. Bleiben interleave_hit hoch, kontrolliere ich, ob die Policy beabsichtigt aktiv ist. Zeigt other_node Zuwachs ohne Lastanstieg, prüfe ich verdrängende Workloads. So wird die Tabelle zum Startpunkt für zielgerichtete Schritte.
NUMA‑Topologie verstehen und nutzen
Bevor ich tune, prüfe ich die Topologie des Servers: Sockel, Kerne, Speicherkanäle, Latenzpfade. Läuft ein Prozess auf Sockel 0, hält aber die Arbeitssätze auf Sockel 1, steigt die Zugriffszeit. Das drückt Durchsatz und lässt Antwortzeiten schwanken. Besonders speicherhungrige Dienste spüren jede unnötige Ferndistanz. Ich lege daher datenintensive Prozesse an Knoten mit ausreichend freiem RAM an.
Asymmetrische Anbindungen verstärken Effekte, etwa wenn ein Knoten weniger Kanäle nutzt. In solchen Fällen verschiebe ich gecachten Datenbestand gezielt, statt den Prozess zu verteilen. VM‑ und Container‑Hosts setze ich so auf, dass jede Instanz eine konsistente Knotenbindung erhält. Damit reduziere ich Ferndatenverkehr ohne Quoten zu drosseln. Die Physik der Maschine setzt die Leitplanken, ich halte mich daran.
Interleave und Balancing richtig einordnen
Interleave‑Policies verteilen Speicher bewusst über Knoten, damit Durchsatz pro Prozess steigt oder Hotspots sinken. In dieser Konfiguration zählen hohe interleave_hit als erwünscht. Ich prüfe dann vor allem Gleichmäßigkeit, nicht absolute Lokalität. AutoNUMA beziehungsweise NUMA‑Balancing kann helfen, aber nicht in jeder Lage.
Ich entscheide situativ, ob automatisches Balancing aktiv bleibt. Bei konsistenten, langlaufenden Diensten setze ich eher feste Bindungen. Bei wechselnden Lasten kann AutoNUMA sinnvoll reagieren. Ein guter Überblick zu Nutzen und Risiken liegt im Beitrag NUMA Balancing. Erst wenn Ziel und Rahmen klar sind, wähle ich den geeigneten Modus.
Workload‑Muster: Datenbanken, VMs, Webdienste
Datenbanken reagieren empfindlich auf Latenz zwischen CPU und RAM. Ich halte daher Instanz, Buffer Cache und aktive Shards auf demselben Knoten. Virtuelle Maschinen profitieren von klaren CPU‑Sets plus Knoten‑RAM, damit Gastbetriebssysteme konsistente Wege sehen. Webdienste mit vielen Workern glänzen, wenn Worker‑Gruppen an einen Knoten gebunden bleiben. Für die Speicherstrategie nutze ich je nach Fall gezielte NUMA Memory Policies.
Analytische Jobs und große Scans arbeite ich dagegen teils verteilt ab. Hier bringt Interleave oft bessere Bandbreiten als harte Lokalität. Wichtig bleibt, die IO‑Muster des Workloads ehrlich zu betrachten. Schreiben dominiert anders als Lesen, zufällige Zugriffe anders als sequentielle. Ich wähle die Policy, die zum Zugriffsmuster passt, nicht die, die im Lehrbuch gut klingt.
Praktische Messroutine und Werkzeuge
Für den Start genügen mir numastat und die Prozessansicht. Ich protokolliere Zählerstände mit Datum, PID und Lastindikatoren. Wichtig ist dabei, über identische Zeitfenster zu messen. So lassen sich Vorher/Nachher‑Unterschiede sauber abbilden. In produktiven Fenstern schreibe ich die Deltas weg und korreliere sie mit Release‑Zeitpunkten.
Bei auffälligen Diensten prüfe ich zusätzlich die CPU‑Bindung und Knotensicht mit Tools wie lscpu, numactl und perf‑Sicht auf Fernlast. Ich dokumentiere je Messrunde die gewählte Policy. Nach einer Änderung messe ich wieder. Erst wenn sich die Trendlinien stabil verlagern, bewerte ich den Effekt als gelungen. Blindes Umschalten führt leicht zu Scheinverbesserungen.
Häufige Fehlinterpretationen vermeiden
Ein hoher interleave_hit ist kein Fehler, wenn Interleave beabsichtigt aktiv ist. Genauso ist ein einzelner Miss bei langer Laufzeit vernachlässigbar. Ich prüfe immer Dichte und Verteilung über das Intervall, nicht nur Spitzen. Manche lesen other_node als pauschal negativ und übersehen den Workload‑Charakter. Ich schaue mir deshalb erst das Designziel an und bewerte dann die Zahlen.
Ein weiterer Irrtum: Gesamtsicht gut, damit alles gut. Häufig verstecken sich Ausreißer nur in einigen PIDs. Oder Container scheduler verteilen Pods quer über Knoten, obwohl eine lokale Gruppe sinnvoll wäre. Solche Effekte sehe ich erst, wenn ich pro Prozess messe. Ohne diese Tiefe bleibt die Analyse halbgar.
Tuning‑Schritte mit Wirkung
Ich beginne mit Platzierung: Prozesse auf den Knoten, wo Daten liegen oder liegen sollen. Dann setze ich CPU‑Affinity, damit Threads nicht quer über Sockel springen. Die Speicherbindung folgt, damit der Kernel am gewünschten Ort allokiert. Für variable Lasten prüfe ich Policies und falls passend AutoNUMA.
Anschließend sorge ich für Konsistenz im Lebenszyklus: Neustarts, Deployments und Skalierungen dürfen Knotenbezüge nicht zufällig ändern. Ich dokumentiere Bindungen als Code, damit sie reproduzierbar bleiben. Danach messe ich erneut, bewerte die Deltas und entscheide über Feintuning. Jede Änderung verdient einen klaren Messbeweis.
Praxisbeispiel: Von Miss zu Hit
Angenommen, eine Datenbank zeigt unter Last mehr numa_miss und steigendes other_node. Die Latenz der Queries schwankt stärker. Ich prüfe zuerst die Prozessbindung und stelle fest, dass nach einem Rollout der Dienst auf Knoten A läuft, der Cache jedoch auf Knoten B allokiert wurde. Nach fester CPU‑ und Speicherbindung an Knoten B kippt das Verhältnis: numa_hit steigt, Misses sinken. Die Antwortzeiten werden konstanter, und die CPU‑Last fällt leicht, weil Fernzugriffe wegfallen.
Parallel kontrolliere ich die Policy. Interleave war unabsichtlich aktiv und hat Allokationen verteilt. Nach Umstellung auf bevorzugten Knoten bleibt der Cache geschlossen lokal. Nach einer Stunde Messung bestätigen die Deltas die Verbesserung. Erst dann erkläre ich das Tuning als Erfolg. Ohne diese Gegenprobe hätte eine Momentaufnahme getäuscht.
Metriken und Richtwerte für die Praxis
Für Entscheidungen nutze ich belastbare Quoten statt einzelner Rohwerte. Ich berechne je Prozess die Allokationsquote local_alloc = numa_hit / (numa_hit + numa_miss). Zusätzlich werte ich die Zugriffsquote local_access = local_node / (local_node + other_node) aus. Beide zusammen zeigen, ob Speicher auch nach der Allokation lokal genutzt bleibt. Als grobe Richtwerte gelten mir: Bei latenzkritischen Diensten peile ich remote‑Zugriffe unter 5–10 % an. Bei analytischen Bandbreiten‑Workloads toleriere ich 20–30 %, sofern der Durchsatz steigt. Entscheidend ist die Stabilität über Zeit. Eine Quote, die unter Last sauber konstant bleibt, ist mir lieber als ein kurzer Peak mit perfekten Werten. Ich dokumentiere diese Zielkorridore pro Service, damit spätere Messungen klar einzuordnen sind.
Cgroups, Container und Scheduler‑Fallen
In Container‑Umgebungen prüfe ich zuerst die cpuset‑Zuordnung: CPU‑Sets und cpuset.mems müssen denselben Knotenraum abbilden, sonst entstehen zwangsläufig Misses. Ich stelle sicher, dass Pods mit festen CPU‑Requests nicht über mehrere NUMA‑Knoten gestreckt werden, und dass der Scheduler keine Worker derselben Applikation quer verteilt. Bei Burst‑Typen grenze ich die max. Threads je Pod so ein, dass sie innerhalb eines Knotens bleiben. Ich dokumentiere die NUMA‑Domäne pro Deployment und verlange konsistente Replikate (eine Worker‑Gruppe pro Knoten, nicht halbe Gruppen über zwei Knoten). Wenn ich die Speichergröße pro Pod knapp kalkuliere, schaffe ich mir ungewollten Druck: Ein leichtes Headroom pro Knoten verhindert, dass der Kernel früh auf Fremdknoten ausweichen muss. Werden Container häufig neu gestartet, achte ich auf deterministische Bindung, damit Cold‑Starts nicht zufällig schlechtere Lokalität bekommen.
Virtuelle Maschinen und vNUMA konsistent ausrichten
Bei VMs richtet sich meine Aufmerksamkeit auf vNUMA: Die virtuelle Topologie muss zur physischen passen. Ich teile vCPUs so, dass jeder vNUMA‑Knoten auf genau einem physischen NUMA‑Knoten landet. Hostseitig pinne ich die QEMU/Hypervisor‑Threads in diese Domäne und stelle sicher, dass der zugewiesene RAM vollständig aus diesem Knoten bereitgestellt wird. Ballooning und Overcommit nehme ich für latenzkritische VMs zurückhaltend in Kauf; aggressives Ballooning kann Hotsets aus dem Knoten drängen und remote‑Zugriffe hochtreiben. Bei Live‑Migration verifiziere ich nach dem Move die Bindungen erneut – manche Umgebungen verlieren dabei die fein gesetzte CPU‑ und Speicherbindung. Erst wenn die vNUMA‑Ausrichtung sauber ist, bewerte ich systemweit numastat: Sonst repariere ich Symptome, nicht die Ursache.
THP, Hugepages und Page‑Migration
Transparent Huge Pages (THP) können sowohl helfen als auch stören. Größere Seiten reduzieren TLB‑Misses und verbessern Bandbreite, doch wenn der Kernel Hugepages erst spät kollabiert oder migriert, können unpassende Ferndistanzen entstehen. Ich halte mich an zwei Regeln: Erstens, die Policy klar definieren (z. B. bevorzugter Knoten) und Allokationen möglichst von Beginn an lokal groß anlegen. Zweitens, bei Workloads mit festen, großen Caches setze ich nach Möglichkeit statische Hugepages (hugetlb), die ich explizit auf einem Knoten reserviere. Das verringert Fragmentierung und kompensationsbedingte Ausweichbewegungen. Sehe ich in Zeitreihen, dass nach längerer Laufzeit der other_node‑Anteil steigt, prüfe ich, ob Page‑Migration oder Kompaktion stattfindet und ob THP‑Einstellungen zum Muster passen. Wichtig ist mir: Nicht global abschalten oder einschalten – ich entscheide pro Dienst und messe den Effekt auf Lokalität und Latenz.
Reclaim, Swap und Memory‑Pressure richtig lesen
Steigen Misses ohne offensichtliche Platzierungsänderung, suche ich nach Speicherdruck pro Knoten. Volle Knoten zwingen den Kernel zu Reclaim und Kompaktion, teils ausgelöst durch kswapd auf einem anderen Knoten – das erzeugt seiteneffekte bei den Zählern. Ich prüfe pro Node den freien Speicher und die Auslastung des Page‑Caches. Aktiviertes Swap kann Hotsets verdrängen und Latenzen explodieren lassen; für besonders empfindliche Dienste deaktiviere ich Swap oder begrenze ihn strikt. In Logs und perf‑Sichten suche ich nach Reclaim‑Peaks während Lastspitzen. Ziel ist, genug freien, lokalen RAM auf dem Zielknoten zu halten, damit Allokationen nicht wegdriften. Wenn dafür Caches reduziert werden müssen, priorisiere ich den Arbeitssatz des Dienstes gegenüber generischem Page‑Cache.
Praktische Befehle und Auswertung
Für die Prozesssicht nutze ich numastat -p <pid> und ergänze mit cat /proc/<pid>/numa_maps, um Allokationen pro Bereich (anonym, File‑Backed) und pro Knoten zu sehen. numactl --hardware gibt mir Latenzmatrizen und Knotengrößen, lscpu --extended zeigt die CPU‑Zuordnung zu Knoten. Für Speicherzugriffe mit Fokus auf Fernpfade setze ich perf mem ein, um Lastmuster zu verifizieren. Deltas sammele ich reproduzierbar, z. B.:
Messroutine
- t0: numastat (gesamt) und numastat -p für Top‑PIDs sichern
- 30–60 s Last fahren, identische Workload‑Phase
- t1: numastat erneut lesen, Deltas pro Zähler berechnen
- Parallel CPU‑, Kontextwechsel‑ und Knoten‑Speicherwerte loggen
Ich rechne mir im Anschluss die Quoten und markiere, welche Prozesse signifikant von den systemweiten Trends abweichen. Wo Unsicherheit bleibt, wiederhole ich die Messung mindestens dreimal. Erst konsistente Deltas bewerte ich als verlässlich. Für persistentes Monitoring mappe ich die Zähler auf Zeitreihen und verknüpfe sie mit Release‑Metadaten – so erkenne ich Regressionspunkte sofort.
Checkliste für geordnetes NUMA‑Tuning
- Ziel definieren: Latenz vs. Durchsatz, Fixlast vs. variabel
- Topologie erfassen: Knoten, Latenzen, freie RAM‑Reserven pro Knoten
- Baseline messen: numastat gesamt und pro Prozess, Quoten ableiten
- Platzierung korrigieren: CPU‑Affinity, Speicherbindung, Policies
- Container/VMs ausrichten: cpuset.cpus = Knoten, cpuset.mems passend; vNUMA sauber mappen
- THP/Hugepages bewusst wählen, Fragmentierung im Blick
- Memory‑Pressure reduzieren: Headroom je Knoten, Swap‑Strategie prüfen
- Nachmessen: Deltas vergleichen, Stabilität über Zeit sicherstellen
- Dokumentieren: Bindungen als Code, Releasenotes mit NUMA‑Kontext
Kurz zusammengefasst
Ich werte NUMA‑Zahlen aus, indem ich Signale im Zusammenhang lese: Zählerpaare, Zeitverläufe und Prozesssicht. Die Schlüsselwerte numa_hit, numa_miss, numa_foreign, local_node, other_node und interleave_hit zeigen mir Lokalität, Ausweichbewegungen und Verteilungsstrategien. Ich entscheide auf Basis der Topologie und des Workloads, nicht anhand starrer Grenzwerte. Tuning beginnt mit Platzierung, Affinity, passender Policy und sauberer Messroutine. So liefere ich konstante Performance, weil CPU und RAM zur Anwendung passen und Fernwege selten bleiben.


