...

NUMA Memory Policies für große Datenbankserver: Performance gezielt optimieren

NUMA Memory entscheidet bei großen Datenbankservern, wie nah Threads am benötigten Speicher arbeiten und wie stark Latenzen Antwortzeiten und Durchsatz beeinflussen. Ich stimme CPU-Zuordnung, Speicherplatzierung und Workload-Größe gezielt ab, reduziere entfernte Zugriffe und erreiche damit eine verlässliche, planbare Performance.

Zentrale Punkte

  • Topologie verstehen: Knoten, Kerne, RAM und Interconnect gezielt berücksichtigen.
  • Policies passend wählen: Strict, Preferred, Interleave nach Workload-Ziel.
  • Affinität umsetzen: Threads, IRQs und Speicher lokal binden.
  • VMs auf Knotenmaß: vCPU und RAM in einen NUMA-Knoten legen.
  • Monitoring durchziehen: Remote-Reads, P99-Latenz und Knotenlast messen.

NUMA-Topologie verstehen

Ich starte jede Optimierung mit der Topologie: Wie viele NUMA-Knoten existieren, wie verteilen sich Kerne, wie ist der RAM an die Sockel angebunden, und wie teuer sind Interconnect-Zugriffe. Lokaler Speicherzugriff kostet deutlich weniger Zeit als ein Zugriff über Knotengrenzen, deshalb vermeide ich unnötige Remote-Wege. Große Datenbankserver profitieren, wenn ich Arbeitsmengen so plane, dass Threads und Daten auf demselben Knoten bleiben. Passt die aktive Datenmenge nicht in einen Knoten, plane ich die Verteilung bewusst statt sie dem Standardverhalten zu überlassen. So halte ich die Latenz niedrig und ermögliche einen gleichmäßigen Durchsatz selbst bei hoher Last.

BIOS- und Hardware-Setup sauber wählen

Ich prüfe im BIOS, dass Node Interleaving deaktiviert ist, damit die NUMA-Trennung erhalten bleibt. Speicherkanäle belege ich symmetrisch pro Sockel und achte auf die Bestückung (1DPC vs. 2DPC), damit Takt und Bandbreite nicht unnötig einbrechen. Features wie C-States und aggressive Energiesparmodi trimme ich bei Latenzzielen konservativer, damit Kerne nicht dauernd aufwachen müssen. SMT/Hyper-Threading bewerte ich workloadspezifisch: Für stark speichergebundene OLTP-Workloads limitiere ich parallel aktive SMT-Threads pro Kern, um Cache-Druck und Variabilität zu reduzieren. Ich verifiziere außerdem, dass PCIe-Geräte (NICs, NVMe) pro Sockel lokal angebunden sind, damit deren IRQs und DMA-Pfade nicht quer über den Interconnect laufen. Wer hier gründlich aufsetzt, baut die Basis, auf der Policies und Affinitäten ihre Wirkung entfalten.

Memory Policies richtig wählen

Die Wahl der Policy steuert, von welchem Knoten der Kernel Speicher allokiert und wie Fallbacks aussehen. Strict setzt harte Grenzen und bricht Allokationen ab, wenn der Zielknoten keinen Platz hat; das priorisiert Leistung über Flexibilität. Preferred hält einen bevorzugten Knoten, fällt bei Knappheit aber auf andere zurück und bietet damit einen Mittelweg. Interleave verteilt Seiten im Round-Robin-Verfahren über mehrere Knoten, was bei sehr großen, gleichmäßig genutzten Daten sinnvoll sein kann. Für viele Datenbanken ist eine lokale Strategie mit Preferred oder Strict meist die bessere Wahl.

Policy Verhalten Typische Nutzung Vorteile Risiken
Strict Nimmt Speicher nur vom Zielknoten, sonst Fehler Latentzkritische Datenbanken mit klarer Knotenplanung Maximal lokale Zugriffe, planbare Latenzen Allokation kann scheitern, wenn der Knoten voll ist
Preferred Bevorzugter Knoten, Fallback auf andere möglich Allgemeine Workloads mit wechselnder Last Gute Nähe bei akzeptabler Flexibilität Mehr Remote-Anteile bei Knappheit
Interleave Round-Robin über mehrere Knoten Sehr große, breit genutzte Daten Verteilte Auslastung mehrerer Knoten Schwächere Lokalität, potenziell höhere Latenz

Threads, CPU-Affinität und Speicherbindung

Ich pinne Threads auf Kerne des Zielknotens, binde Speicher mit numactl und richte IRQs so aus, dass Daten lokal bleiben. Diese Kopplung aus CPU-Affinität und Memory Binding reduziert teure Remote-Reads und macht die Laufzeitverteilung enger. Für granulare Kontrolle nutze ich Policies auf Prozess- oder Thread-Ebene und halte den Buffer Pool so nahe wie möglich an den aktiven Worker-Threads. Wer tiefer einsteigen will, findet praktische Schritte zur CPU-Affinität, die sich direkt auf produktiven Hosts anwenden lassen. So sichere ich konsistente Latenzen auch dann, wenn das System stark ausgelastet ist.

Lokale Hotsets priorisieren

Ich identifiziere Hotsets der Workload und platziere sie strikt lokal, während kalte Daten eher flexibel liegen dürfen. Durch diese Priorisierung bleibe ich bei Kernpfaden nah am RAM des Knotens. Steigt die Last, skaliert die Lösung sauber, weil die teuren Pfade weiterhin lokal laufen. Ohne diese Ordnung gerät die Latenzkurve ins Rutschen, sobald Threads vermehrt quer über Knoten zugreifen. Eine klare Bindung verhindert genau dieses Verhalten zuverlässig.

Storage- und Netzwerk-NUMA zusammenführen

Ich ordne NICs und NVMe-Geräte gezielt den Sockeln zu und lenke ihre IRQs auf lokale Kerne. Receive-/Transmit-Steering (RSS/RPS/XPS) halte ich pro Knoten konsistent, damit Pakete dort verarbeitet werden, wo auch die Datenbank-Threads laufen. Bei NVMe nutze ich mehrere Warteschlangen pro Kern und pinne IO-Threads lokal, sodass Log- und Datenpfade nicht über den Interconnect pendeln. Für Replikation trenne ich Netzpfade pro Knoten, damit eingehende WAL/Redo-Streams lokal landen. So bleiben IO– und CPU-Pfade kongruent, und die Datenbank verschenkt keine Zyklen an unnötige Kopien quer durch den Speicherverbund.

VMs auf Knotenmaß planen

Ich dimensioniere VMs so, dass vCPU-Zahl und RAM in einen physischen NUMA-Knoten passen, denn das senkt Latenz und Interconnect-Verkehr. Wide VMs, die größer als ein Knoten sind, verteilen zwangsläufig Speicherzugriffe und verlieren damit Vorhersagbarkeit. Wenn eine VM größer sein muss, plane ich vNUMA explizit und achte auf eine symmetrische Aufteilung über die Knoten. Für die Host-Seite meide ich Oversubscription bei Latenz-Workloads und halte den lokalen Speicher pro VM reserviert. Einen schnellen Überblick zur physischen Knotenstruktur liefert „NUMA-Knoten planen“, was Entscheidungen zur VM-Größe erleichtert und Fehler bei der Platzierung vorbeugt.

Hypervisor-Einstellungen beachten

Ich prüfe, wie der Hypervisor vNUMA präsentiert, und halte die Zuordnung von vCPU-Gruppen zu physischen Kernen konsistent. Zusätzlich sorge ich dafür, dass die NUMA-Topologie der VM auf die des Hosts passt, damit der Scheduler lokal bleiben kann. Speicherreservierungen und Anti-Affinity-Regeln halte ich so knapp wie möglich, aber so strikt wie nötig. Hohe VM-Dichte auf einem Sockel ersetze ich lieber durch Knoten-nahe Verteilung. So reste ich Remote-Zugriffe auf ein Minimum und halte die IO-Pfade stabil.

Container- und Orchestrierungs-Praxis

In Containern setze ich cpuset-Grenzen konsistent: CPUs und zugehörige Speicher-Masken (cpuset.cpus, cpuset.mems) gehören zusammen. Systemd-Slices und Units erhalten feste CPU-Affinitäten, damit der Kernel die Speicherbevorzugung auch wirklich durchsetzt. In Orchestrierungslayern plane ich Pods/Services knotennah, nutze Topologiebewertungen und statische CPU-Zuweisung, damit ein Workload nicht zwischen Knoten pendelt. Huge Pages deklariere ich explizit pro Pod/Container und halte deren Größe und Anzahl pro Knoten stabil. Wichtig: Infrastruktur- und Nebenprozesse (Logging, Sidecars, Backups) binde ich auf andere Kerne oder sogar auf den jeweils anderen NUMA-Knoten, um Hotsets der Datenbank nicht zu stören.

NUMA-Balancing und Betriebssystem-Tuning

Automatisches NUMA-Balancing kann lokale Zugriffe verbessern, wenn Workloads wandern oder sich Phasen stark ändern. Ich nutze es gezielt, beobachte aber, ob das Verschieben von Seiten hin und her mehr stört als nutzt. Fest verdrahtete Prozesse mit klarer Affinität profitieren häufig von manuell gesetzten Policies statt von ständiger Umlagerung. Kernel-Parameter, IRQ-Aussteuerung und transparente Huge Pages prüfe ich jeweils im Kontext der Datenbank und der Plattform. Als Startpunkt hilft mir dieser NUMA-Balancing-Leitfaden, um Einstellungen schrittweise zu testen und die Streuung der Latenzen zu senken.

Huge Pages gezielt einsetzen

Ich setze Huge Pages ein, um TLB-Misses zu reduzieren und große Speicherbereiche effizienter zu adressieren. Für Datenbankserver reserviere ich die Seiten vorab, ordne sie Knoten zu und prüfe, ob die Instanz sie tatsächlich nutzt. Transparent Huge Pages deaktiviere ich oft bei Latenz-Zielen und setze statische Huge Pages, damit die Allokation deterministisch bleibt. Entscheidend bleibt aber die Nähe zum NUMA-Knoten; Huge Pages verstärken eine gute Strategie, ersetzen sie aber nicht. Wer das ignoriert, gewinnt kaum Leistung und riskiert Seiteneffekte beim Paging.

Datenbanken dimensionieren: Buffer Pool und Arbeitsmenge

Ich plane die aktive Arbeitsmenge so, dass Buffer Pool, Lock- und Plan-Caches und die heißesten Tabellen in einen Knoten passen. Bei sehr großen Instanzen teile ich Services oder Shards entlang der Knoten, statt eine riesige monolithische Instanz über alle Knoten zu spannen. Für OLTP-Fälle halte ich den Buffer Pool pro Knoten kompakt und priorisiere lokale Hit-Raten. Für OLAP-Scans kann Interleave in Sonderfällen sinnvoll sein, wenn die Datenmenge gigantisch und gleichmäßig ist. Ohne diese Disziplin wächst der Interconnect-Verkehr und frisst Reserven genau dann, wenn Lastspitzen auftreten.

Datenbank-spezifische Kniffe

Ich berücksichtige das Prozess- und Thread-Modell der Engine: PostgreSQL nutzt Prozesse, daher pinne ich die Hauptinstanz, Autovacuum und Checkpointer je Knoten getrennt und halte shared_buffers pro Shard lokal. Bei MySQL/InnoDB mappe ich buffer pool instances auf Knoten und richte IO-Threads und Log-Writer lokal aus. SQL Server profitiert von angepasstem Soft-NUMA und einer Zuweisung, die Schedulers und Speichergruppen entlang der physischen Knoten gliedert. Oracle-Instanzen setze ich mit lokalen Large Pages auf und segmentiere Worker- und IO-Server entlang der Knoten. Allgemein reduziere ich Arena-Contention des Allocators (z. B. jemalloc) durch NUMA-bewusste Arenen und sorge dafür, dass Lock Manager und Latch-Hotspots lokal bleiben, indem ich Partitionierung und Sharding entlang der Knoten ziehe.

Monitoring: Metriken, die zählen

Ich messe Remote-Reads, Node-Interconnect-Traffic, Page Faults pro Knoten und die P99-Latenz der relevanten Queries. Zusätzlich beobachte ich CPU-Auslastung je Knoten, NUMA-Miss-Verhältnisse und den Anteil lokaler Speicherzugriffe. Diese Sicht zeigt, ob die Policy wirkt oder ob Threads unkontrolliert auf entfernte Seiten zugreifen. Ich korreliere Peaks mit Scheduler-Entscheidungen, Migrationsereignissen und Allokationsfehlern. Erst diese Metriken bestätigen, dass die Policy nicht nur im Labor, sondern dauerhaft im Produktivsystem trägt.

Teststrategie und Rollout

Ich teste in Stufen: Zuerst Mikrobenchmarks zur Bandbreite und Latenz pro Knoten, dann realistische Workloads mit kalten und warmen Caches. Laststufen fahre ich schrittweise hoch, messe P95/P99/P99.9 und beobachte die Verteilung, nicht nur Mittelwerte. Ich dokumentiere jede Änderung (Policy, Affinitäten, Huge Pages, IRQ-Lenkung) und vergleiche A/B unter identischen Rahmenbedingungen. Vor dem Rollout definiere ich Abbruchkriterien und einen Backout-Plan, damit ich bei Regressionen schnell zur vorherigen Konfiguration zurückkehre. Ein kurzer Soak-Test unter Dauerlast deckt Drift und Migrationen auf, die in kurzen Läufen unsichtbar bleiben.

Schritt-für-Schritt-Vorgehen

Zuerst erfasse ich die Topologie: Anzahl Knoten, Kernzuordnung, Speicherkanäle und Interconnect. Danach bestimme ich die Ziel-Workload je Knoten und prüfe, ob Hotsets hinein passen. Im nächsten Schritt setze ich CPU-Affinität, IRQ-Lenkung und Memory Binding auf Prozess- oder Thread-Ebene. Dann aktiviere oder deaktiviere ich NUMA-Balancing abhängig von der Dynamik der Workload und reserviere bei Bedarf Huge Pages pro Knoten. Zuletzt verifiziere ich das Ergebnis mit wiederholbaren Lasttests und überwache Kennzahlen im Dauerbetrieb.

Praxisbeispiele und Stolpersteine

Eine OLTP-Instanz mit vielen kurzen Transaktionen gewinnt messbar, wenn ich Worker-Threads und Buffer Pool auf einen Knoten festlege und Strict oder Preferred setze. Ein Data-Warehouse mit breiten Scans kann von Interleave profitieren, wenn die Daten sehr gleichmäßig genutzt werden und die Knoten gut ausgelastet sind. VMs verlieren spürbar an Planbarkeit, sobald sie über Knotengrenzen wachsen und der Hypervisor Speicher versetzt zuteilt. Ich sehe häufig, dass eine einzige „weite“ VM den Interconnect überlastet und damit auch Nachbar-VMs verlangsamt. Diese Effekte verschwinden, sobald ich zu lokaler Zuordnung und sauberer vNUMA-Konfiguration zurückkehre.

Fehlerszenarien und Anti-Pattern

Mit Strict erhöhe ich das Risiko, dass Allokationen scheitern und der OOM-Killer eingreift. Deshalb halte ich Kopfkissen auf dem Zielknoten frei, monitore Fehlversuche und definiere Fallbacks (z. B. gezieltes Resize außerhalb der Hauptlastzeiten). Transparent Huge Pages im always-Modus verursachen in Latenzpfaden Defragmentierung und Stalls – ich nutze statische Reservierungen oder setze THP auf madvise. Automatisches NUMA-Balancing kann Seiten bei oszillierender Last hin- und herschieben; erkenne ich Page-Bounce-Muster, setze ich die Policies wieder manuell. In VMs sind Ballooning und Memory-Kompression Gift für Vorhersagbarkeit; diese Funktionen deaktiviere ich für kritische Datenbanken. Live-Migrationen über Knoten plane ich nur mit Downtime-Fenstern oder verlagere zuerst die Daten bankseitig, damit der Interconnect nicht sekundär dichtläuft.

Kapazitätsplanung und Wachstum

Ich plane pro Knoten eine Reserve von 10–20 % für Lastspitzen, Autovacuum/Compaction und periodische Wartungsjobs ein. Wächst die Datenmenge, skaliere ich zuerst entlang der Knoten (Shards/Services), statt blind den gesamten Buffer Pool zu vergrößern. Ich unterbinde stilles „Schleichwachstum“ durch harte Grenzwerte je Knoten und Alerts, sobald lokale Hit-Raten sinken oder Remote-Anteile steigen. Bei Projektionen für die nächsten Quartale berücksichtige ich nicht nur Datenvolumen, sondern auch Transaktionsraten und veränderte Zugriffsverteilungen, denn diese verschieben Hotsets häufig schneller als der reine Speicherbedarf. So bleibt die Plattform stabil – und Erweiterungen erfolgen kontrolliert, ohne die NUMA-Lokalität zu opfern.

Kurzbilanz

Ich optimiere große Datenbankserver, indem ich NUMA-Topologie, Policies und Workload-Größe sauber zusammenführe. Lokale Speicherzuordnung bringt die entscheidenden Millisekunden, während ungeplante Remote-Zugriffe die P99-Latenz aufblähen. VMs plane ich künftens so, dass sie in Knoten passen oder vNUMA klar nutzen. Betriebssystemeinstellungen, Affinitäten und Huge Pages setze ich gezielt ein, prüfe ihre Wirkung und rolle Änderungen nur mit Messpunkten aus. Wer diese Schritte beherzigt, holt die erwartete Leistung aus moderner Hardware und hält die Plattformen auch unter hoher Last verlässlich schnell.

Aktuelle Artikel