Consulting, Beratung
NUMA – Grundlagen und Anwendung in SQL Server 2022Grundlagen von NUMA (Non-Uniform Memory Access)
Was ist NUMA? NUMA (Nicht-uniformer Speicherzugriff) ist eine Architektur für Mehrprozessorsysteme, bei der jeder Prozessor über einen eigenen lokalen Arbeitsspeicher verfügt. Alle Prozessoren teilen sich zwar einen gemeinsamen Adressraum (das heißt, jeder Prozessor kann auf den gesamten Speicher zugreifen), jedoch hängt die Zugriffsgeschwindigkeit davon ab, ob die Daten im lokalen Speicher des Prozessors liegen oder im Speicher eines anderen Prozessors. Im Unterschied zur traditionellen UMA-Architektur (Uniform Memory Access) gibt es bei NUMA also „nahe“ Speicherzugriffe mit geringer Latenz und „ferne“ Speicherzugriffe mit höherer Latenz. Bei UMA hingegen wird der gesamte Speicher zentral verwaltet und von allen Prozessoren mit uniformer (gleicher) Zugriffsgeschwindigkeit erreicht – dies ist einfach in der Programmierung, skaliert aber nur begrenzt, da alle Prozessoren um dieselbe Speicherbandbreite konkurrieren. NUMA dagegen steigert die Skalierbarkeit von Mehrprozessorsystemen, indem es den Speicher in Knoten aufteilt und so die gleichzeitige Speichernutzung effizienter gestaltet. Jeder NUMA-Knoten umfasst typischerweise einen Prozessor (bzw. einen Sockel mit mehreren Prozessorkernen) und den diesem Prozessor direkt zugeordneten Arbeitsspeicher. Die Verbindung zwischen den Knoten erfolgt über eine Hochgeschwindigkeits-Schnittstelle, sodass ein Prozessor auch auf den Speicher anderer Knoten zugreifen kann – jedoch langsamer als auf den eigenen. Moderne Multiprozessor-Systeme (insbesondere Server mit zwei oder mehr CPU-Sockeln) implementieren fast ausnahmslos eine NUMA-Architektur, da reine UMA-Systeme jenseits eines Sockels aufgrund von Engpässen bei Speicherbandbreite und -latenz nicht effizient skalieren.
Vorteile von NUMA:
– Bessere Skalierbarkeit: NUMA ermöglicht es, zusätzliche Prozessoren mit eigenem Speicher hinzuzufügen, ohne dass alle CPUs um einen einzigen zentralen Speicherbus konkurrieren müssen. Dadurch lassen sich weit mehr Kerne und größere Speichermengen in einem System nutzen, als es in einer UMA-Architektur möglich wäre. Jeder Knoten steuert eigenen Speicher bei, was die Gesamt-Speicherbandbreite des Systems erhöht.
– Lokalitätsprinzip für Speicherzugriffe: Da jeder Prozessor bevorzugt auf seinen lokalen Arbeitsspeicher zugreift, profitieren Anwendungen von geringerer Latenz und höherer Bandbreite, solange ihre Daten im lokalen Speicher gehalten werden. Häufig benötigte Daten können pro Knoten lokal vorgehalten werden, was schnellere Zugriffszeiten für die zugehörigen Prozessoren bedeutet.
– Weniger Contention: Im Vergleich zu UMA reduziert NUMA die Wettbewerbskonflikte um den Speicher. Jeder Knoten hat einen eigenen Speichercontroller und dedizierte Speicherkanäle. Das verteilt die Last und vermeidet den Flaschenhals eines einzigen Speichercontrollers. Besonders in speicherintensiven Anwendungen (z. B. Datenbanken) kann NUMA dadurch eine deutlich bessere Performance erzielen, da sich Speicherzugriffe auf mehrere Knoten parallelisieren lassen.
Herausforderungen und Nachteile von NUMA:
– Nicht-uniforme Speicherzugriffszeiten: Die wichtigste Herausforderung ist bereits im Namen enthalten – die Zugriffszeiten sind nicht einheitlich. Ein Zugriff auf fremden (remote) Speicher ist spürbar langsamer als auf lokalen Speicher. Je nach Hardware-Generation kann ein entfernter Speicherzugriff leicht 1,5- bis 3-mal so lange dauern. Diese Latenzunterschiede erhöhen die Komplexität bei der Softwareentwicklung, da Performance-Probleme auftreten können, wenn eine Anwendung Daten häufig über Knoten-Grenzen hinweg abruft.
– Komplexität für Betriebssystem und Software: NUMA erfordert eine intelligente Verwaltung durch das Betriebssystem. Das OS muss entscheiden, auf welchem Prozessor Threads laufen und aus welchem Speicher neuer Speicherplatz alloziert wird, um die Speicherlokalität zu optimieren. Betriebssysteme benötigen spezielle Routinen und Scheduler, um NUMA effizient zu unterstützen. Auch Anwendungen selbst müssen idealerweise „NUMA-aware“ sein (NUMA-bewusst programmiert sein), um optimale Leistung zu erzielen. Für Entwickler bedeutet dies einen höheren Aufwand: Man muss das Laufzeitverhalten auf mehreren Knoten berücksichtigen (z. B. Daten möglichst lokal halten, Thread-Affinität nutzen usw.).
– Lastverteilung und Vorhersehbarkeit: In einem NUMA-System kann es zu ungleichmäßiger Lastverteilung kommen. Wenn beispielsweise zufällig ein großer Anteil der Arbeitslast auf einen Knoten fällt (sei es CPU-Last oder Speicherbedarf), während andere Knoten unterfordert sind, kann dies die Gesamtperformance beeinträchtigen. Dynamisches Verschieben von Threads oder Speicher zwischen Knoten ist schwierig und oft ineffizient. Die Performance eines Systems mit NUMA kann daher in bestimmten Situationen weniger vorhersehbar sein als bei UMA, da es darauf ankommt, wie gut die Arbeitslast auf die Knoten verteilt ist. Insbesondere Anwendungen, die viele gemeinsame Daten nutzen oder eng gekoppelte Threads auf verschiedenen Knoten haben, können unter NUMA unrund laufen, wenn keine Gegenmaßnahmen getroffen werden.
– Cache-Kohärenz-Overhead: Moderne NUMA-Systeme sind in der Regel cache-coherent (ccNUMA). Das bedeutet, dass die Hardware sicherstellt, dass alle Prozessoren einen konsistenten Sicht auf den Speicherinhalt haben, auch wenn Daten in den lokalen Caches mehrerer CPUs gehalten werden. Diese Cache-Kohärenz über Knoten hinweg verursacht zusätzlichen Overhead in der Hardware – Interconnects müssen Protokolle zum Abgleichen der Caches fahren, was Traffic auf dem Verbindungsbus erzeugt. Bei intensiver Knoten-übergreifender Kommunikation (wenn mehrere CPUs schnell hintereinander dieselben Speicherstellen aktualisieren) kann dies die Leistung spürbar bremsen. Das Betriebssystem versucht daher, solche Szenarien zu minimieren, indem es z. B. Threads, die viel miteinander interagieren, auf demselben Knoten unterbringt. Trotzdem bleibt die Cache-Kohärenz ein notwendiger Aufwand, der bei falscher Lastverteilung zum Performance-Killer werden kann.
– Kosten und Hardwareaufwand: NUMA-Hardware ist komplexer und meist teurer in der Herstellung. Anstatt nur eines Speichercontrollers und eines zusammenhängenden Speicherpools müssen mehrere Speichercontroller, ein schnelles Verbindungsnetz (für die Knoten-Kommunikation) und ggf. zusätzliche Logik für Kohärenz und Routing verbaut werden. Dies erhöht die Systemkosten. Allerdings sind solche Systeme in der Serverwelt Standard, da der Nutzen in puncto Skalierbarkeit die Mehrkosten rechtfertigt.
Zusammengefasst bietet NUMA enorme Vorteile für leistungsfähige Serversysteme, geht aber mit erhöhter Komplexität einher. Wichtig ist, dass sowohl das Betriebssystem als auch rechenintensive Anwendungen das NUMA-Prinzip „verstehen“ und möglichst ausnutzen. Andernfalls kann ein NUMA-System unter Last seine theoretische Leistung nicht voll ausschöpfen oder, im schlimmsten Fall, schlechter performen als ein einfacheres UMA-System.
Aufbau moderner NUMA-Systeme auf Hardware-Ebene
Moderne Server-Hardware setzt NUMA durch eine Kombination aus mehreren CPU-Sockeln und lokal angeschlossenem Speicher um. Die typische Konfiguration besteht aus zwei oder mehr Prozessor-Sockeln auf dem Mainboard, von denen jeder eigene Speicherkanäle zu lokal installierten RAM-Modulen besitzt. Ein NUMA-Knoten entspricht in der Praxis meist einem CPU-Sockel plus den zugehörigen Speichermodulen auf diesem Socket. Jeder Prozessor kann über seinen integrierten Speichercontroller sehr schnell auf den lokal an „seinen“ Speicherkanälen befindlichen RAM zugreifen. Soll eine CPU hingegen auf Speicher zugreifen, der physisch an einem anderen Prozessor-Sockel angebunden ist, muss der Zugriff über die Verbindung zum anderen Sockel erfolgen – dies ist der entfernte bzw. Remote-Speicherzugriff.
Um dies zu ermöglichen, sind die Sockel untereinander mit speziellen Hochgeschwindigkeits-Verbindungsbussen gekoppelt. Beispiele dafür sind bei Intel die UPI/QPI (Ultra Path Interconnect, früher QuickPath Interconnect) und bei AMD das Infinity Fabric. Diese Verbindungen übernehmen den Datentransfer zwischen den Prozessoren und ermöglichen, dass ein Prozessor auf den Speicher des anderen zugreifen kann, wobei diese Transfers langsamer sind als lokal. Die Topologie solcher Verbindungen kann je nach System variieren (Point-to-Point-Verbindungen zwischen jedem Sockel-Paar, Ring- oder Mesh-Topologie bei mehr als zwei Sockeln usw.), doch das Ziel ist immer, die Knoten so effizient wie möglich zu vernetzen.
Ein einfaches Beispiel ist ein Zwei-Sockel-Server: Er besteht aus zwei NUMA-Knoten, Sockel 0 und Sockel 1. Jeder Sockel hat z. B. 8 CPU-Kerne und eigene Speichermodule (nehmen wir an, jeweils 256 GB RAM). Wenn ein Thread auf Sockel 0 Daten verarbeiten muss, die in dem 256-GB-Speicher von Sockel 0 liegen, erfolgt der Zugriff direkt und mit minimaler Latenz. Greift derselbe Thread jedoch auf Daten zu, die im Speicher von Sockel 1 liegen, muss die Anfrage über den Interconnect an Sockel 1 geleitet werden, und die Daten wandern zurück über diesen Bus zu Sockel 0. Die Latenz ist dabei deutlich höher, außerdem teilen sich beide Prozessoren die Bandbreite des Verbindungsbusses. Die Praxis zeigt, dass die Speicherlatenz in solch einem entfernten Zugriff deutlich steigt und auch der Durchsatz je nach Auslastung niedriger sein kann. Deshalb ist es so wichtig, möglichst lokale Speicherzugriffe zu fördern.
Bei Vier- oder Acht-Sockel-Systemen setzt sich dieses Prinzip fort: Jedes Paar von Sockeln kommuniziert über Interconnects, oft in komplexeren Topologien (beispielsweise ein vollständiges Gitter oder hierarchische Switched Fabrics). Die Grundidee bleibt aber: Das System wird in mehrere Knoten partitioniert, die intern schnellen Zugriff haben und untereinander mit etwas langsameren Verbindungen gekoppelt sind.
Auch Single-Sockel-Systeme sind der Vollständigkeit halber zu erwähnen: Ein einzelner Prozessor mit zugehörigem Speicher stellt trivially einen NUMA-Knoten dar. In so einem System gibt es faktisch keine entfernten Speicherzugriffe – alle Zugriffe sind uniform (UMA). Deshalb bezeichnet man Ein-Sockel-Maschinen (oder generell Systeme, bei denen alle CPUs am gleichen Speicher hängen, ohne Unterschiede in der Latenz) als UMA-Systeme. Sobald jedoch mehr als ein physischer Sockel im Spiel ist, hat man es mit NUMA zu tun.
Eine Besonderheit sind moderne Prozessoren mit Multi-Chip-Module oder Chiplet-Architektur, etwa AMD EPYC CPUs. Hierbei befinden sich mehrere Rechendies auf einem Sockel, die sich den Speichercontroller teilen oder sogar eigene Speichercontroller haben. Solche Designs können dazu führen, dass selbst innerhalb eines Sockels mehrere NUMA-Domänen konfiguriert werden können. Bei früheren EPYC-Generationen war es etwa möglich, einen einzelnen physischen Sockel logisch als zwei oder vier NUMA-Knoten erscheinen zu lassen (dies nennt sich NPS = Nodes Per Socket), um die Speicheraffinität zu verbessern. Das Betriebssystem sieht in diesem Fall pro Sockel mehrere Knoten, je nachdem wie die CPU ihre Ressourcen intern aufteilt. Diese Technik unterstreicht, dass das Konzept „NUMA-Knoten“ immer eine Kombination aus Recheneinheiten plus lokalem Speicher meint – egal ob diese auf separaten physischen CPUs liegen oder innerhalb eines komplexen Prozessors aus mehreren Dies bestehen.
Zusammenfassung der Hardware-Ebene: Jeder NUMA-Knoten verfügt über eine bestimmte Anzahl CPU-Kerne und einen lokalen Abschnitt des Hauptspeichers. Die Knoten sind über dedizierte Hochgeschwindigkeitsbusse verbunden. Lokale Speicherzugriffe innerhalb eines Knotens sind schnell, während Zugriffe über die Knoten-Grenze hinweg langsamer sind. Die Hardware stellt sicher, dass trotz der verteilten Speicher alle Prozessoren auf den gesamten Speicher zugreifen können (ein einziges Systemimage) und dass Datenkonsistenz gewährleistet bleibt (Cache-Kohärenzprotokolle). Diese Architektur bildet die Grundlage, auf der Betriebssysteme und Anwendungen aufsetzen müssen, um die bestmögliche Leistung zu erzielen.
Auswirkungen auf Betriebssysteme und Anwendungen
Die Einführung von NUMA bringt eine Verantwortung für das Betriebssystem mit sich: Es muss die unterschiedlichen Speicherzugriffszeiten berücksichtigen und Prozesse/Threads sowie Speicherzuteilungen möglichst günstig auf die vorhandenen Knoten verteilen. Moderne Betriebssysteme wie Windows, Linux und Unix-Derivate besitzen daher eine NUMA-Unterstützung in ihrem Kernel und Scheduler. Konkret bedeutet das:
- Der Scheduler (Prozess-/Thread-Zuteiler) versucht, Threads möglichst auf den Prozessoren desselben NUMA-Knotens laufen zu lassen, auf dem sie gestartet wurden oder wo ihr überwiegender Speicherbedarf liegt. Wenn ein Prozess initial einem bestimmten Knoten zugewiesen wurde, bleibt er – soweit möglich – mit seinen Threads auf diesem Knoten „verankert“. Das Betriebssystem vermeidet es also, einen laufenden Thread unnötig auf einen Kern eines anderen NUMA-Knotens zu verschieben (Thread-Migration), da dies dazu führen würde, dass der Thread fortan hauptsächlich auf fremden Speicher zugreift.
- Das Speichermanagement des OS nutzt in der Regel eine First-Touch-Strategie: Der physische Speicher für eine bestimmte Speicheranforderung (z. B. eine Speicherseite für einen Prozess) wird auf dem NUMA-Knoten allokiert, auf dem der ausführende Thread läuft, der die Anforderung verursacht hat. Durch dieses „erste Berührung“-Prinzip sorgt das OS automatisch dafür, dass neu belegter Speicher im Normalfall lokal zum anfordernden Thread liegt. Eine Anwendung muss dafür nichts Besonderes tun – es ist die Standardstrategie, um Speicherlokalität zu fördern.
- Betriebssysteme stellen zudem APIs bereit, mit denen Anwendungen explizit auf NUMA-Topologie Einfluss nehmen können. In Windows gibt es beispielsweise Funktionen, um Prozessor-Affinitäten festzulegen (man kann einem Prozess oder Thread bestimmte CPU-Kerne bzw. einen bestimmten Knoten zuweisen) und Speicher auf einem bestimmten Knoten zu reservieren (etwa via Windows API VirtualAllocExNuma, womit Speicher gezielt in einem angegebenen NUMA-Knoten allokiert wird). Unter Linux existiert analog die libnuma bzw. Funktionen wie mbind und sched_setaffinity, mit denen man das NUMA-Verhalten steuern kann. Diese Mechanismen werden vor allem von NUMA-sensitiven Serveranwendungen oder speziellen HPC-Programmen genutzt.
Trotz dieser Betriebssystemunterstützung hängt viel von der Anwendungssoftware selbst ab. Eine Anwendung, die nicht NUMA-fähig (NUMA-unaware) ist, verlässt sich voll auf das OS. Das kann in einfachen Fällen ausreichen: Solange die Anwendung nur wenige Threads hat oder ihre Threads hauptsächlich getrennte Daten bearbeiten, wird das OS-intelligente Scheduling oft schon gute Ergebnisse liefern. Probleme treten aber dann auf, wenn die Anwendung viele Threads über alle CPUs verteilt oder umfangreiche gemeinsame Datenstrukturen nutzt, ohne die Daten auf die Knoten aufzuteilen. Beispiele:
- Eine multithreaded Anwendung, die Globale gemeinsame Daten im Speicher hält und auf allen Kernen parallel darauf zugreift, kann auf einem NUMA-System erheblichen Performanceverlust erleiden. Da die Threads auf verschiedenen Knoten laufen könnten, greifen manche unweigerlich remote auf diese globalen Daten zu. Außerdem führen die ständigen Updates der gemeinsamen Daten zu verschärfter Cache-Kohärenz-Kommunikation über die Knoten hinweg. In solchen Fällen skaliert die Anwendung unter NUMA schlechter als erwartet, weil der Vorteil der lokalen Speicheraufteilung nicht zum Tragen kommt.
- Eine Anwendung, die ihre Threads wild auf alle verfügbaren CPUs verteilt, könnte ebenfalls ineffizient arbeiten. Zwar versucht das OS, die First-Touch-Regel zu nutzen, aber wenn ein Thread im Verlauf seiner Lebenszeit oft auf anderen Knoten ausgeführt wird (z. B. durch OS-Zeitplanung oder Thread-Pooling), dann wird sein „alter“ Speicher plötzlich remote. Die zuvor lokal allokierten Daten liegen dann auf einem anderen Knoten als der laufende Thread. Das führt zu vermeidbarer Latenz.
- Speicherlastige Workloads wie In-Memory-Caches oder große Datenbanken müssen besonders aufpassen: Wenn sie nicht NUMA-optimiert sind, kann es passieren, dass eine disproportionale Menge Speicher auf einem Knoten belegt wird und ein anderer Knoten noch freien Speicher hat. Das OS würde dann ab einem gewissen Punkt Speicheranforderungen auf den anderen, noch freien Knoten ausweichen (weil der lokale voll ist). Dieser Fall – ein Prozess verteilt seinen belegten Speicher über mehrere NUMA-Knoten – resultiert in viel „fremdem“ Speicherzugriff. Die Anwendung kann spürbar langsamer werden, weil jeder Zugriff prüfen muss, ob die Daten im lokalen RAM oder auf dem entfernten liegen.
Die Lösung für diese Herausforderungen liegt in NUMA-Awareness auf Anwendungsebene (siehe nächster Abschnitt). Dennoch sei betont: Betriebssysteme haben in den letzten Jahren ausgefeilte Strategien entwickelt, um auch nicht optimierte Programme auf NUMA-Hardware ordentlich laufen zu lassen. So gibt es etwa in Windows den ideal processor und ideal node Mechanismus, bei dem jedem Thread ein bevorzugter NUMA-Knoten zugeordnet wird, auf dem er primär laufen soll. Außerdem können OS-Scheduler Lastunterschiede zwischen Knoten erkennen und notfalls Threads verschieben, um z. B. einen komplett idle stehenden Knoten mit Aufgaben zu versorgen, falls andere völlig überlastet sind. Solche Aktionen sind jedoch immer Abwägungen zwischen besserer Lastverteilung und Verlust von Speicherlokalität.
Für Anwendungsentwickler bedeutet all dies: Um maximale Performance auf moderner Server-Hardware zu erzielen, sollte man NUMA im Hinterkopf behalten. Bei serverseitigen Programmen oder Diensten, die auf großen Maschinen laufen, lohnt es sich, explizit zu testen, wie sich die Software verhält, wenn sie auf zwei oder mehr NUMA-Knoten verteilt ist. In vielen Fällen kann schon die richtige Konfiguration (z. B. ausreichend viele Threads, aber nicht mehr als nötig pro Knoten; sinnvolles Platzieren von Daten und ggf. Aufteilen von Workloads) drastisch zur Stabilität und Planbarkeit der Leistung beitragen.
NUMA-Awareness: Was bedeutet NUMA-fähige Software?
Als “NUMA-aware” bezeichnet man Software, die speziell dafür entwickelt oder konfiguriert ist, auf NUMA-Hardware optimal zu funktionieren. NUMA-fähige Software erkennt die zugrundeliegende NUMA-Topologie und verhält sich so, dass sie die Vorteile der lokalen Speicherzugriffe nutzt und die Nachteile (remote Zugriffe) minimiert. Aber was heißt das konkret? Einige wichtige Merkmale und Techniken von NUMA-fähiger Software sind:
- Topologie-Erkennung: Eine NUMA-bewusste Anwendung ermittelt zunächst, wie viele NUMA-Knoten das System hat und welche CPU-Kerne zu welchem Knoten gehören. Diese Information stellen Betriebssysteme über APIs zur Verfügung. Die Software kann z. B. abfragen: „Gibt es 2 oder 4 Knoten? Wie viele CPUs pro Knoten?“ Auf Basis dessen lässt sich die eigene Arbeitsweise anpassen (z. B. Thread-Pools pro Knoten anlegen).
- Thread-Affinität und -Bindung: NUMA-optimierte Programme vermeiden es, Threads unkontrolliert zwischen Knoten hin- und herzuschieben. Häufig wird pro NUMA-Knoten eine feste Gruppe von Threads vorgesehen, die bevorzugt auf den Kernen dieses Knotens laufen. Man spricht auch von Prozessor-Affinität – die Threads werden an „ihren“ Knoten gebunden. Das kann durch OS-Funktionen geschehen (z. B. gezielt Threads auf CPU 0–7 binden, die zu Knoten 0 gehören, usw.) oder durch Nutzung von Thread-Pools, die von vornherein knotenbasiert arbeiten. Das Ergebnis ist, dass jeder Thread im Normalfall immer auf demselben Knoten arbeitet und damit dessen lokalen Speicher bevorzugt nutzt.
- Speicher-Affinität: Analog zur Thread-Bindung achtet NUMA-fähige Software darauf, Speicher lokal anzufordern, wo es sinnvoll ist. Wenn beispielsweise ein bestimmter Verarbeitungsthread hauptsächlich Datenstruktur X bearbeitet, dann sollte Speicher für X auf demselben Knoten liegen, auf dem der Thread läuft. Viele Systeme realisieren das, indem sie sicherstellen, dass der Thread selbst die initiale Allokation und Initialisierung der Daten übernimmt (First-Touch-Prinzip, wie schon beim OS erwähnt). Es gibt aber auch die Möglichkeit, mit speziellen Aufrufen direkt Speicher in einem Knoten zu reservieren.
- Datenpartitionierung nach Knoten: Ein effektives Muster ist, globale Daten in knotenspezifische Portionen aufzuteilen. Anstatt z. B. eine einzige große Liste von Aufgaben zu verwalten, könnte eine NUMA-bewusste Anwendung pro NUMA-Knoten eine eigene Aufgabenliste führen. Threads von Knoten 0 bearbeiten dann nur die Liste 0, Threads von Knoten 1 nur Liste 1 usw. Dadurch bleiben die meisten Zugriffe lokal. Ein anderes Beispiel sind Datenbanken, die ihre Pufferspeicher oder ihren Arbeitscache in Segmente pro NUMA-Knoten unterteilen – jeder Knoten cached „seine“ zuletzt genutzten Daten separat. Solche Partitionierung verringert den Bedarf, dass ein Knoten auf Speicher eines anderen zugreift.
- Knoten-lokale Dienste und Threads: In vielen serverartigen Anwendungen werden bestimmte Hintergrundarbeiten pro Knoten vervielfacht. So kann es etwa pro NUMA-Knoten einen eigenen I/O-Thread oder einen eigenen Garbage-Collector-Thread geben. Diese Threads kümmern sich um auf ihrem Knoten anfallende Aufgaben (z. B. I/O-Anfragen, Speicherbereinigung) und nutzen dafür lokalen Speicher. Dieser Ansatz stellt sicher, dass selbst systemnahe Aufgaben (z. B. Logging, Flushes, Hintergrundberechnungen) nicht zu viele bereichsübergreifende Speicheraktionen verursachen.
- Bewusste Lastverteilung: Ist Software NUMA-aware, so entscheidet sie oft bewusst, auf welchem Knoten neue Arbeit ausgeführt wird. Beispielsweise könnte ein Server eingehende Client-Verbindungen abwechselnd verschiedenen NUMA-Knoten zuordnen (Round-Robin-Verteilung). Damit verteilt man die Last gleichmäßig und verhindert, dass zufällig alle schweren Abfragen auf Knoten 0 landen, während Knoten 1 nichts tut. Wichtig ist aber, dass – hat eine Verbindung einmal „ihren“ Knoten – sie dort bleibt, damit Folgetätigkeiten (die oft zusammenhängen) lokal bearbeitet werden können.
- Konfigurationsmöglichkeiten: Viele komplexe Serveranwendungen bieten Einstellungen, mit denen Administratoren die NUMA-Verteilung beeinflussen können. Beispielsweise kann man bei einigen Applikationsservern einstellen, dass sie pro NUMA-Knoten separate Thread-Pools verwenden sollen. Oder bei In-Memory-Datenbanken lässt sich festlegen, welcher Knoten wie viel vom Speicherbudget bekommt. Diese Optionen erlauben Feintuning, falls die automatische Logik nicht ideal passt.
Beispiele für NUMA-fähige Software sind Datenbanksysteme (wie Microsoft SQL Server, Oracle, PostgreSQL – alle haben Mechanismen zur NUMA-Unterstützung), große Web- und Applikationsserver, Virtualisierungshypervisoren (Hyper-V, VMware ESXi, etc.), aber auch wissenschaftliche HPC-Anwendungen, die mit Hilfe von Bibliotheken (z. B. NUMA-API auf Linux) speziell optimiert werden.
Andererseits existiert viel Standardsoftware, der NUMA praktisch egal ist – z. B. einfache Tools, Hilfsprogramme oder Dienste, die nur wenige Threads nutzen oder kaum speicherintensive Operationen durchführen. Diese laufen meist auch ohne spezielle Anpassung ordentlich, solange das OS sie verwaltet. Die größten Gewinne durch NUMA-Awareness erzielt man in Szenarien mit hoher Parallelität und hohem Speicherverbrauch, wenn also die Wahrscheinlichkeit von Cross-Node-Zugriffen groß ist. Hier zahlt sich die Mühe aus, weil man den Unterschied zwischen lokalen und entfernten Zugriffen deutlich spüren kann.
Zusammengefasst bedeutet NUMA-fähige Software, dass die Software aktiv dafür sorgt, „Daten dicht bei der Rechenarbeit“ zu halten. Sie nutzt die Architektur so, dass möglichst viel lokal passiert und die vorhandenen Resourcen pro Knoten optimal ausgelastet werden, während zugleich teure Knoten-übergreifende Operationen reduziert werden.
NUMA in Microsoft SQL Server 2022
Microsoft SQL Server (alle Editionen, einschließlich SQL Server 2022) ist von Haus aus eine NUMA-aware Anwendung. Das Datenbank-Managementsystem wurde explizit dafür entwickelt, auf NUMA-Hardware effizient zu laufen, ohne dass der Benutzer große manuelle Anpassungen vornehmen muss. Im Folgenden betrachten wir detailliert, wie SQL Server NUMA erkennt und nutzt, welche Auswirkungen das auf die Performance, die Abfrageverarbeitung, Thread-Verwaltung und Speicherverwaltung hat, und welche Best Practices es für die Konfiguration gibt. Zudem beleuchten wir verfügbare Tools zur Diagnose sowie besondere Aspekte bei der Virtualisierung von SQL-Server-Workloads auf NUMA-Systemen.
Erkennung und automatische Nutzung von NUMA durch SQL Server
Wenn der SQL Server-Dienst startet, überprüft er zunächst die vom Betriebssystem gemeldete Hardwaretopologie. Insbesondere ermittelt SQL Server die Anzahl der NUMA-Knoten, die Anzahl der CPUs (bzw. logischen Prozessoren) pro Knoten und ob Hyper-Threading aktiv ist. Diese Informationen protokolliert SQL Server im Fehlerlog zum Start – dort findet man Einträge wie etwa: „SQL Server detected 2 sockets with 8 cores per socket and 16 logical processors per socket, 32 total logical processors“. Anhand dieser Daten richtet SQL Server intern seine Scheduler-Struktur ein: Es wird pro erkannter CPU ein Scheduler im sogenannten SQLOS (SQL Server Operating System) erstellt. Diese Scheduler sind in Gruppen nach NUMA-Knoten organisiert. Konkret bedeutet das: hat das System z. B. 2 NUMA-Knoten mit jeweils 16 logischen Prozessoren, so erzeugt SQL Server 32 Scheduler-Objekte, gruppiert in 2 Node-Strukturen (Knoten 0 mit 16 Scheduler, Knoten 1 mit 16 Scheduler).
Wichtig zu wissen ist, dass SQL Server automatisch Soft-NUMA einsetzen kann, falls ein einzelner Hardware-Knoten sehr viele CPU-Kerne hat. Soft-NUMA bezeichnet eine rein softwareseitige Aufteilung eines NUMA-Knotens in mehrere virtuelle Knoten, um die Last besser zu verteilen. Ab SQL Server 2016 wurde dies eingeführt: Standardmäßig unterteilt SQL Server jeden Hardware-NUMA-Knoten, der mehr als 8 physische Kerne umfasst, in kleinere Soft-NUMA-Knoten. Die Idealgröße liegt dabei bei 8 Kernen pro Soft-Knoten (mindestens 4, höchstens 8). Dieses Verfahren läuft vollautomatisch beim Start von SQL Server ab und wird im Fehlerlog vermerkt (z. B. „Automatic soft-NUMA was enabled because SQL Server detected hardware NUMA nodes with greater than 8 physical cores“). Das Ziel von Soft-NUMA ist es, interne Strukturen und Threads in handhabbareren Gruppen zu halten, was die Skalierbarkeit verbessert. Soft-NUMA beeinflusst nicht die Speicherzuordnung (d. h. es ändert nichts daran, wo Speicher physisch liegt), sondern wirkt sich primär auf die CPU-Seite aus – insbesondere auf die Verteilung von Verbindungssitzungen, Aufgaben und einigen System-Threads.
Nach der Initialisierung der Knoten und Scheduler richtet SQL Server pro NUMA-Knoten bestimmte systemeigene Threads ein. Dazu gehören insbesondere: – Lazy Writer: Ein Hintergrund-Thread, der sich um das Flushing („Herausschreiben“) geänderter Pufferseiten aus dem Speicher auf Datenträger kümmert, wenn Speicher frei gemacht werden muss. SQL Server startet in der Regel pro NUMA-Knoten einen Lazy-Writer-Thread. Damit hat jeder Speicherbereich eines Knotens seinen eigenen „Hausmeister“, was verhindert, dass ein einzelner Thread alle z. B. terabytes an Buffer Pool global verwalten muss.
– I/O Completion Thread: Für asynchrone Ein-/Ausgabe (speziell für Netzwerk und Datei-I/O) setzt SQL Server auf I/O Completion Ports. Auch hier wird pro Knoten mindestens ein entsprechender Thread eingerichtet, der I/O-Operationen abschließt und Ergebnisse entgegennimmt. So wird z. B. sichergestellt, dass wenn Knoten 0 eine I/O-Anfrage an die Platte stellt, der Abschluss dieser Operation von einem Thread auf Knoten 0 bearbeitet wird (was wiederum nahelegt, dass die betroffenen Daten dann auch lokal weiterverarbeitet werden).
Zusätzlich sind viele interne Speicherstrukturen und Caches in SQL Server NUMA-bewusst gestaltet. Zum Beispiel wird der Buffer Pool (Datenpuffer) – der größte Speicherbereich von SQL – über die vorhandenen Knoten aufgeteilt. Das heißt, statt eines einzigen zusammenhängenden Heaps von z. B. 200 GB stellt SQL Server intern pro NUMA-Knoten einen eigenen Pufferbereich zur Verfügung (z. B. auf einem System mit zwei Knoten jeweils ~100 GB pro Knoten, sofern beide Knoten gleich viel physikalischen RAM haben). Eine ähnliche Partitionierung findet für bestimmte andere Caches und Strukturen statt, um Lock-Contention zu reduzieren. SQL Server muss natürlich dennoch im Hintergrund ein einheitliches Bild der Daten sicherstellen (es darf z. B. eine bestimmte Datenbankseite nicht in zwei Knotenpuffern gleichzeitig zwei Kopien haben). Dies geschieht durch zentrale Koordination, aber der Zugriff der Worker-Threads erfolgt bevorzugt auf dem knoten-lokalen Cache.
Alles in allem gilt: SQL Server erkennt NUMA automatisch und nutzt diese Architektur, ohne dass der Administrator eingreifen muss. Selbst in der kostenlosen Express Edition oder der Standard Edition (die im Lizenzumfang gewisse CPU-Beschränkungen hat) ist die NUMA-Fähigkeit vorhanden. Es gibt in den meisten Fällen keinen Schalter „NUMA ein/aus“ – das System stellt sich selbst darauf ein, wenn es eine NUMA-Umgebung vorfindet. Einstellungen wie Affinity Mask oder Soft-NUMA-Konfiguration bestehen zwar (für Edge-Cases oder spezielle Tuning-Maßnahmen), sind aber im Normalbetrieb nicht erforderlich. Microsoft hat SQL Server so entworfen, dass er out-of-the-box auf NUMA-Hardware gut läuft.
Einfluss von NUMA auf Performance, Abfrageverarbeitung und Parallelität
Die Präsenz von NUMA in einem Server kann spürbare Auswirkungen auf die SQL-Server-Performance und das Verhalten von Abfragen haben. Generell strebt SQL Server an, so viel wie möglich lokal pro Knoten zu verarbeiten, um die beste Geschwindigkeit zu erzielen. Dennoch gibt es einige Punkte, die das Laufzeitverhalten beeinflussen:
- Lokale vs. Remote-Speicherzugriffe: Solange eine Abfrage nur Daten verarbeitet, die im lokalen Speicherbereich ihres ausführenden Threads liegen, arbeitet sie optimal – Speicherzugriffe erfolgen mit minimaler Latenz. Sobald jedoch Daten aus dem Puffer eines anderen NUMA-Knotens benötigt werden, müssen diese über den Interconnect angefordert werden. Dies dauert länger und kann gerade bei vielen zufälligen Zugriffen die CPU-Auslastung beeinflussen (die CPU wartet öfter auf Daten). In SQL Server äußert sich das zum Beispiel darin, dass eine Abfrage auf Daten, die überwiegend im „fremden“ Speicher liegen, mehr CPU-Zeit pro verarbeitetem Datensatz benötigt als dieselbe Abfrage, wenn die Daten vorher im lokalen Knoten zwischengespeichert waren. In Zahlen: Remote-Zugriffe können je nach Hardware um Größenordnungen 10–50 % langsamer sein pro Zugriff. Für einzelne Abfragen ist das schwer direkt zu messen, aber über viele Millionen Zugriffe summiert es sich. SQL Server versucht diese Situation abzumildern, indem oft benötigte Seiten auf jedem Knoten bei Bedarf vorgehalten werden (allerdings nie doppelt gecached, sondern der Thread wechselt dann faktisch auf den entfernten Speicherzugriff). Ideal ist es jedoch, wenn sich Workloads so aufteilen lassen, dass jeder Knoten primär „seine“ Daten verarbeitet.
- Parallele Abfragen und MAXDOP: SQL Server verwendet Parallelität für Abfragen (wenn erlaubt), um große Anfragen auf mehrere CPU-Kerne zu verteilen. In einer NUMA-Umgebung stellt sich dabei die Frage, ob die parallel arbeitenden Threads auf einem einzigen Knoten bleiben oder sich über mehrere Knoten spannen. Im Allgemeinen versucht der SQL Server Scheduler, die für eine parallele Abfrage benötigten Threads auf demselben NUMA-Knoten zu platzieren, solange dort genügend freie Scheduler verfügbar sind. Dadurch profitieren alle Threads von gemeinsamen lokalen Daten und schnellen Cache-Coherence zwischen den Kernen eines Knotens. Wenn aber sehr viele Threads nötig sind oder ein Knoten ausgelastet ist, kann es vorkommen, dass eine parallele Abfrage Threads in mehreren Knoten hat. Das führt dann dazu, dass Teilergebnisse der Abfrage über Knoten-Grenzen hinweg ausgetauscht werden müssen (zum Beispiel bei Joins oder Aggregationen in parallelen Plänen müssen die Daten der verschiedenen Threads eventuell zusammengeführt werden – sind diese Threads auf unterschiedlichen Knoten, fließen die Daten über den Interconnect). Dies kann die Laufzeit der Abfrage erhöhen und zu weniger konsistenter Performance führen. Eine oft empfohlene Best Practice ist daher, den Maximalen Grad an Parallelität (MAXDOP) so zu konfigurieren, dass eine einzelne Abfrage nicht mehr Threads verwendet, als Kerne in einem NUMA-Knoten vorhanden sind. Beispiel: Hat ein System 2 Knoten mit je 16 Kernen, sollte MAXDOP im Bereich 8–16 gesetzt werden, typischerweise auf 16, damit eine Abfrage höchstens 16 parallel arbeitende Threads nutzt und diese im Optimalfall komplett auf einen Knoten passen. Dadurch vermeidet man „knotenübergreifende“ Parallelität. (Nebenbei begrenzt Microsoft in neueren Empfehlungen MAXDOP generell auf 16, selbst wenn ein Knoten z. B. 32 Kerne hätte, da jenseits von 16 der zusätzliche Nutzen oft gering ist.)
- Workload-Verteilung und Round Robin: SQL Server verteilt neue Benutzerverbindungen standardmäßig im Round-Robin-Verfahren auf die vorhandenen NUMA-Knoten. Das heißt, wenn mehrere Clients sich verbinden oder mehrere Anfragen ohne spezifizierte Affinität hereinkommen, werden sie abwechselnd Knoten 0, Knoten 1, Knoten 2, usw. zugewiesen. Dadurch soll sich die Last gleichmäßig über die Knoten verteilen. In der Praxis funktioniert das für viele gleichartige Sessions gut – kein Knoten wird komplett übergangen. Allerdings kann es bei bestimmten Konstellationen (z. B. in der Standard Edition mit Lizenzbeschränkung, siehe weiter unten) dazu kommen, dass die Knoten nicht symmetrisch viele CPUs haben oder einige Scheduler deaktiviert sind. Dann kann eine strikt gleichmäßige Verteilung der Verbindungen zu einer tatsächlichen Last-Ungleichheit führen. Normalerweise jedoch gilt: wenn alle Knoten dieselbe Anzahl aktiver CPU-Kerne haben, wird bei vielen parallelen Anfragen das OS und SQL dafür sorgen, dass beide Knoten ungefähr ähnlich ausgelastet werden. Sollte dennoch einmal ein Knoten deutlich höher ausgelastet sein als der andere (z. B. CPU 100 % auf Node 0, aber nur 50 % auf Node 1), dann lohnt es sich zu analysieren, ob vielleicht bestimmte große Abfragen immer auf demselben Knoten laufen oder ob andere Faktoren (Affinity-Einstellungen, Ungleichverteilung der Daten) eine Rolle spielen.
- Speicherverteilung und Ausnutzung: Dank NUMA hat jeder Knoten einen eigenen Teil des SQL Server-Speichers (Buffer Pool Nodes). Das bedeutet aber auch, dass Speicherengpässe knotenweise auftreten können. Wenn eine Arbeitslast primär auf Knoten 0 riesige Mengen an Daten in den Cache lädt, kann es sein, dass der zugeordnete Speicherbereich von Node 0 vollständig gefüllt ist, während Node 1 noch freien Puffer hat. In so einer Situation muss SQL Server damit beginnen, Speicheranforderungen von Node 0 im fremden Speicher von Node 1 zu bedienen (diese Zuteilungen nennt man „Foreign Pages“ oder fremd-kommittierten Speicher). Solche Foreign Pages sind ein Anzeichen für Speicher-Ungleichgewicht: Ein Knoten muss auf den RAM des anderen zurückgreifen. Die Performance der auf Node 0 laufenden Queries kann dadurch leiden, da ein Teil ihrer Daten nun im langsameren remote Speicher liegt. SQL Server versucht über seinen Resource Monitor und Lazy Writer pro Knoten, solche Situationen zu managen – etwa indem es auf Node 0 aggressiver Puffer säubert, um Platz zu schaffen. Dennoch lässt es sich nicht immer vermeiden, dass bei asymmetrischer Last auch asymmetrisch Speicher verbraucht wird. Insgesamt wirkt NUMA hier als Isolation: Probleme (aber auch Reserven) in einem Knoten beeinflussen die anderen nicht sofort. Für die Performance heißt das: Man beobachtet eventuell unterschiedliche Page-Life-Expectancies oder Cache-Hit-Rates je Knoten. Für den DBA ist es daher wichtig, nicht nur global auf Speicherkennzahlen zu schauen, sondern auch je Knoten, um Engpässe zu erkennen.
- Latenz und Durchsatz im gesamten System: In vielen Benchmarks zeigt sich, dass ein NUMA-System bei gleichen Gesamtressourcen leicht höhere Latenzen für einzelne Anfragen haben kann als ein UMA-System, dafür aber viel höheren Gesamtdurchsatz schafft. Der Grund: Die zusätzliche Synchronisation und die ungleichen Zugriffszeiten fügen jeder einzelnen Operation einen gewissen Overhead hinzu (z. B. Locking-Mechanismen über Knoten hinweg dauern minimal länger). Dieser Overhead ist meist sehr gering und fällt für Endanwender kaum ins Gewicht. Gleichzeitig erlaubt NUMA aber, viel mehr gleichzeitig zu verarbeiten (weil eben mehrere Speichercontroller und -bänke parallel arbeiten). So kann ein zweisockliges System beispielsweise doppelt so viele Transaktionen pro Sekunde verarbeiten wie ein einsockliges – jedoch könnte die Antwortzeit einer einzelnen kleinen Transaktion unter Last vielleicht ein paar Millisekunden höher liegen als auf dem Ein-Sockel-Server, bedingt durch die Koordinationskosten zwischen den Knoten. Für OLTP-Workloads (mit vielen parallelen kurzen Transaktionen) ist der Durchsatzgewinn entscheidend und NUMA klar im Vorteil. Für einzelne riesige analytische Abfragen (OLAP) muss man je nach Szenario schauen, ob diese besser auf einem Knoten bleiben oder über alle Knoten verteilt schneller laufen; hier kommen wieder Parallelität und MAXDOP ins Spiel.
In Summe reagiert SQL Server dank NUMA-Unterstützung sehr gut auf Skalierung über mehrere Sockel, aber es ist wichtig, die Konfiguration so zu gestalten, dass unnötige Knoten-übergreifende Arbeit vermieden wird. Die Abfrageverarbeitung profitiert, wenn parallel ablaufende Aktionen lokal bleiben können. Gleichzeitig garantiert die Lastverteilung über Knoten, dass kein Teil des Systems brachliegt, solange andere Teile überlastet sind – was die Gesamtauslastung und damit den Durchsatz maximiert.
Thread-Zuordnung und Speicherverwaltung in NUMA-Umgebungen
Die Thread-Verwaltung (Scheduling) in SQL Server ist eng mit der NUMA-Architektur verzahnt. Wie bereits erwähnt, erstellt SQL Server pro logischem Prozessor einen Scheduler. Jeder Scheduler gehört genau einem NUMA-Knoten an und repräsentiert im Prinzip einen Ausführungs-„Slot“ für SQL-Server-Worker-Threads auf einem bestimmten CPU. Die SQL Server-eigenen Threads (für User-Abfragen, Systemtasks etc.) werden vom SQLOS den Schedulern zugewiesen und dort in Warteschlangen verwaltet (Runnable Queue, Waiter List etc.).
Die Regel lautet nun: Ein Worker-Thread bearbeitet seine Aufgabe immer auf dem Scheduler (CPU) desselben NUMA-Knotens, dem er zugewiesen wurde. Es findet kein automatisches Springen eines laufenden Threads von einem Knoten auf einen anderen statt – das würde dem Konzept widersprechen. Wenn ein Thread gerade rechenaktiv ist und Zeit bekommt, dann auf der CPU seines eigenen Schedulers. Somit kann man sagen: Jeder Thread „lebt“ innerhalb eines bestimmten Knoten, was die Speicherlokalität fördert.
Nun ist es so, dass ein einzelner Benutzerprozess (z. B. eine Clientverbindung zur Datenbank) durchaus Threads auf verschiedenen Knoten haben könnte, aber das passiert nur, wenn er mehrere Threads parallel nutzt und SQL Server diese bewusst auf verschiedene Knoten verteilt. Standardmäßig wird eine Session komplett einem NUMA-Node zugeordnet, sobald sie angelegt wird (Round-Robin, wie oben beschrieben). Alle Abfragen dieser Session laufen daher bevorzugt auf diesem Knoten – entweder seriell als einzelner Thread auf einem CPU dieses Knotens, oder parallel, dann aber nach Möglichkeit mit allen parallel Threads auf demselben Knoten. Diese Strategie – eine Session an einen Node binden – vermeidet, dass eine Einzelsession gleichzeitig die Ressourcen mehrere Knoten strapaziert. Sollte man aus irgendeinem Grund eine Abfrage über Knoten hinweg verteilen wollen, ginge das nur über spezielle Konfiguration (z. B. MAXDOP höher als Kernanzahl pro Node, wie vorher erläutert).
Bei hohem Workload mit vielen Sessions sorgt der Round-Robin-Mechanismus dafür, dass alle Knoten ähnliche Mengen an Sessions und damit Threads betreiben. Dies manifestiert sich im DMF sys.dm_os_schedulers: Man sieht dort je Node X Scheduler, von denen typischerweise alle „VISIBLE ONLINE“ sind (d.h. aktiv) und idealerweise ähnliche Load_Factor oder Runnable-Queue-Längen aufweisen. Wenn in der Praxis doch Abweichungen auftreten (siehe Beispiel in einem späteren Abschnitt bei Standard Edition), kann es sein, dass aufgrund von Lizenzgrenzen oder CPU-Konfiguration nicht alle Scheduler aktiv sind.
Die Speicherverwaltung von SQL Server wurde ebenfalls von Grund auf NUMA-bewusst implementiert. Jedes als Memory Node bezeichnete Element entspricht einem physischen NUMA-Knoten (bzw. Soft-NUMA-Knoten in Einzelfällen). Der größte Verbraucher, der Buffer Pool, ist intern pro Memory Node aufgeteilt. Das heißt beispielsweise: In einem System mit zwei Knoten und 256 GB maximalem Server-Speicher hat jeder Node zunächst einmal ~128 GB als sein Target zugewiesen bekommen. Innerhalb jedes Knotens arbeitet ein eigener Lazy Writer, um den Puffer des Knotens zu pflegen (Seiten säubern, Checkpoints etc.). Wenn ein Query-Thread auf Node 0 eine Datenseite von der Festplatte lädt (etwa weil sie noch nicht im Puffer war), legt er diese Seite im Buffer Pool von Node 0 ab. Soll später ein anderer Thread auf Node 0 dieselbe Seite lesen, findet er sie in seinem lokalen Puffer und spart sich so einen IO – alles bleibt lokal. Greift hingegen ein Thread auf Node 1 auf diese Seite zu, muss er auf den Puffer von Node 0 zugreifen – das ist dann ein Remote-Speicherzugriff. SQL Server würde in diesem Fall nicht eine zweite Kopie der Seite in Node 1 anlegen (der Buffer Pool hält Datenbankseiten immer nur einmal im gesamten Server, um Konsistenz und Speicher zu sparen). Somit entstehen zwangsläufig solche Remote Reads. Allerdings kommen diese seltener vor, wenn die Workload so gestaltet ist, dass Abfragen primär die Daten nutzen, die „ihr“ Knoten zuletzt in den Puffer geholt hat (zum Beispiel, indem jede Partition einer Tabelle überwiegend von Sessions auf demselben Node angefragt wird).
Neben dem Buffer Pool gibt es auch noch andere Speicherregionen, beispielsweise Arbeitsspeicher für Sortierungen, Hash-Joins oder Zwischenresultate (Query Workspace Memory), Thread Stacks, Cachestores für Pläne und Metadaten etc. Ab SQL Server 2012 wurden viele dieser Speicherbereiche in den gemeinsamen Memory Manager integriert. Dennoch passiert die Zuteilung für große Teile davon ebenfalls nach Knoten. Wenn auf Node 0 ein großes Hash-Join-Array benötigt wird, wird SQL Server versuchen, diesen Speicher aus dem Node 0 Memory zu nehmen. Gelingt das nicht (Node 0 hat nichts mehr frei, Node 1 aber schon), kann die Zuteilung über Knoten-Grenzen hinweg erfolgen. Das spiegelt sich in der DMV sys.dm_os_memory_nodes wider: dort gibt es Spalten, die zeigen, wie viel Speicher ein Node „fremd“ allokiert hat bzw. abgegeben hat. Idealerweise sind diese Werte klein – d.h. jeder Node nutzt hauptsächlich seinen eigenen Speicher. Größere Werte für foreign_commit_kb deuten auf das erwähnte Ungleichgewicht hin, das Performance-Kosten haben kann.
Zusätzlich gibt es Pro-Knoten Resource Monitors – jeder NUMA-Knoten hat einen eigenen Überwachungsmechanismus für Speicher-Druck (Memory pressure). Wenn nur ein Node unter starkem Druck steht (viel weniger frei als andere), kann SQL Server gezielt dort eingreifen (Caches schrumpfen etc.), ohne gleich die ganze Instanz zu drosseln. Erst bei globalem Druck (alle Nodes voll) werden global Maßnahmen ergriffen. Das ist ein Vorteil der NUMA-Isolation: lokale Engpässe bleiben lokal, soweit möglich.
Ein weiterer Aspekt sind Spinlocks und Synchronisation im SQL Server. Einige Synchronisationsobjekte sind global (instanzweit), aber viele wurden partitioniert, entweder nach CPU oder nach Node, um parallele Zugriffe zu entzerren. Beispielsweise werden diverse CMEMTHREAD Memory Objects in SQL entweder pro Node oder sogar pro CPU separiert, damit Threads auf verschiedenen Kernen oder Knoten sich nicht unnötig in die Quere kommen. Früher gab es Tuning wie Trace Flag 8048, um Node-gegenüber-CPU-Partitionierung zu erzwingen – heutige Versionen machen das dynamisch. Für das Thema NUMA bedeutet das: Je stärker SQL Server partitioniert, desto mehr versucht er, Operationen innerhalb des Nodes zu halten und globale Locks zu vermeiden. Das erhöht die Skalierbarkeit auf vielen Kernen deutlich.
Zusammengefasst garantiert SQL Server in NUMA-Umgebungen eine knotenbewusste Zuordnung von Threads und Speicher. Ein Workload auf einem bestimmten Node nutzt primär dessen CPU-Kerne und dessen Speicherpool. Solange Ressourcen je Node ausreichen, gibt es kaum „Übersprechen“ zwischen den Knoten – das minimiert Latenzen. Nur wenn nötig, werden Threads oder Speicher über Knoten hinweg eingesetzt, was dann kleine Performanceeinbußen bedeuten kann. Als Administrator sollte man das Verhalten beobachten, um sicherzustellen, dass alle Knoten angemessen genutzt werden und keiner “verdurstet” oder “erstickt”.
Best Practices zur Konfiguration von SQL Server auf NUMA-Systemen
Ein SQL Server 2022 profitiert in der Regel automatisch von NUMA, doch es gibt einige Best Practices und Konfigurationshinweise, mit denen man die Leistung und Stabilität weiter optimieren kann:
- MAXDOP passend zur NUMA-Topologie einstellen: Wie oben erwähnt, sollte der maximale Grad an Parallelismus (Max Degree of Parallelism) so gewählt werden, dass eine parallele Abfrage ihre Threads idealerweise innerhalb eines NUMA-Knotens abarbeiten kann. Ist pro Knoten beispielsweise 12 Kerne verfügbar, empfiehlt sich MAXDOP = 12 (oder leicht darunter, je nach Workload). Microsofts allgemeine Empfehlung lautet: MAXDOP ≤ Anzahl der logischen Prozessoren pro NUMA-Knoten, jedoch nicht mehr als 16. Damit vermeidet man performancefressende Kontextwechsel über Knoten hinweg für einzelne Abfragen.
- Cost Threshold for Parallelism prüfen: Dieser Parameter bestimmt, ab welchem geschätzten Aufwand eine Abfrage parallelisiert wird. In NUMA-Umgebungen kann es sinnvoll sein, den Standardwert (5) zu erhöhen – z. B. auf 30 oder höher – um nicht zu viele kleine Abfragen parallel laufen zu lassen, die womöglich unnötig Threads über Knoten verteilen. Fewer, but larger parallel operations bedeuten oft bessere Ausnutzung. Diese Einstellung ist nicht NUMA-spezifisch, aber indirekt hilft sie, das System nicht mit Parallel-Threads zu überfrachten, was insbesondere auf vielen Kernen relevant ist.
- BIOS- und Hardware-Einstellungen: Stellen Sie sicher, dass im BIOS NUMA aktiviert ist (manche BIOS/UEFI bieten eine Option „Node Interleaving“ oder ähnliches – diese sollte deaktiviert sein, damit das System nicht künstlich auf UMA gestellt wird). Node Interleaving würde den Speicher aller Knoten gleichmäßig mischen und dem OS einen einzigen Knoten vorgaukeln, was zwar Latenzunterschiede halbwegs verwischt, aber insgesamt die Performance deutlich senkt. Daher: BIOS-seitig immer die Standardeinstellung nutzen, die NUMA hervorhebt.
- Speicher gleichmäßig auf Knoten verteilen: In mehrsockeligen Servern sollte darauf geachtet werden, dass die RAM-Bestückung pro Sockel möglichst symmetrisch ist. Jeder NUMA-Knoten sollte gleich viel physikalischen Speicher haben, um SQL Server eine balancierte Basis zu geben. Ungleiche Speicherausstattung (z. B. Sockel 0 hat 64 GB, Sockel 1 aber 128 GB) kann dazu führen, dass der eine Knoten häufiger an Grenzen stößt als der andere. SQL Server allokiert zwar entsprechend (er würde Node 1 mehr Buffer Pool zuteilen als Node 0, wenn mehr RAM da ist), aber es ist einfacher und meist performanter, Symmetrie zu haben.
- Lock Pages in Memory (LPIM) erwägen: Dies ist eine Windows-spezifische Empfehlung: Durch Zuweisen des Benutzerrechts „Speicher bestätigen“ (Lock pages in memory) an den SQL-Server-Dienstaccount stellt man sicher, dass Windows den SQL-Server-Speicher nicht auslagert. In NUMA-Systemen kann Paging besonders schädlich sein, da es die feine Abstimmung der Speicher pro Knoten zunichtemacht. Mit LPIM bleibt der vom SQL Server genutzte Speicher resident im RAM. Somit werden auch NUMA-Nodes nicht unterschiedlich vom Paging betroffen. Hinweis: LPIM muss mit Bedacht eingesetzt werden und erfordert, dass max server memory korrekt konfiguriert ist, damit das OS ausreichend RAM behält.
- Standard Edition Limits berücksichtigen: Die Standard Edition von SQL Server 2022 kann maximal die Lesser of 4 Sockel oder 24 Kerne nutzen (und bis zu 128 GB RAM). Auf einem NUMA-System mit mehr als 4 physischen Knoten oder mehr als 24 Kernen bedeutet das, dass Standard Edition nicht alle CPUs verwenden wird. Die Praxis ist, dass Standard Edition in solchen Fällen die ersten 4 NUMA-Knoten (in OS-Reihenfolge) verwendet und weitere ignoriert. In einer VM-Umgebung kann das problematisch sein, wenn z. B. 8 virtuelle Sockel konfiguriert wurden – Standard würde nur 4 nutzen, wodurch einige vCPUs brachliegen. Best Practice: Bei Verwendung von Standard Edition die VM oder das System so konfigurieren, dass höchstens 4 NUMA-Knoten sichtbar sind und die Kernanzahl 24 nicht überschreitet. Z. B. anstatt „8 Sockets, je 2 Kerne“ lieber „2 Sockets, je 8 Kerne“ zuweisen. So schöpft Standard Edition die CPU-Ressourcen optimal aus und vermeidet seltsame Verteilungen (siehe auch unten „Virtualisierung“).
- Automatische Soft-NUMA aktiviert lassen: Ab SQL 2016 macht die Engine von sich aus Soft-NUMA, wenn sinnvoll. Es gibt eigentlich keinen Grund, das abzuschalten, außer in sehr spezifischen Troubleshooting-Fällen. Durch Soft-NUMA gewinnt man zusätzliche Lazy Writer und I/O Threads und entlastet den Task-Scheduler bei sehr vielen CPUs. Die Einstellung kann via ALTER SERVER CONFIGURATION SET SOFTNUMA = ON/OFF beeinflusst werden, benötigt aber einen Neustart und sollte nur geändert werden, wenn man genau weiß warum. In nahezu allen Fällen: Finger weg – die Standardautomatik ist optimal.
- CPU-Affinität (Affinity Mask) nur bei Bedarf setzen: SQL Server erlaubt es, per affinity mask bestimmte CPUs für die Instanz ein- oder auszuschließen und sogar per affinity I/O mask I/O-Completion-Threads zu binden. Im Normalbetrieb ist davon abzuraten, da der integrierte Scheduler besser dynamisch mit der Last umgehen kann. Affinitätseinstellungen sind Überbleibsel aus früheren Zeiten und können die Engine in ungünstige Situationen zwingen (z. B. ein Node gar nicht genutzt, obwohl er frei ist). Nur in Spezialfällen, etwa wenn man mehrere Instanzen auf dem gleichen Host gezielt trennen möchte (eine Instanz soll ausschließlich auf Node 0 laufen, die andere auf Node 1), greift man zu Affinity Masks – und selbst dann ist es oft eleganter, das OS mittels Prozessorgruppen/CPU-Partitionierung oder VM-Pinning dies regeln zu lassen. Also: Affinity lieber auf „Auto“ (keine gesetzte Maske) belassen.
- Auslastung je Knoten überwachen: Die ideale Situation ist, wenn alle NUMA-Knoten ungefähr gleich ausgelastet sind – sowohl CPU-seitig als auch speicherseitig. In der Praxis können aber bestimmte Datenbankanfragen oder -objekte einen Knoten mehr belasten (z. B. wenn eine bestimmte stark genutzte Tabelle fast vollständig im Node 0 Buffer Pool gecached ist und von dort abgerufen wird). Daher gehört zu den Best Practices ein Monitoring pro Node. Performance Counter wie „% Processor Time“ pro CPU (aggregiert nach Knoten) oder die SQL Server spezifischen Counter „Buffer Node“/„Memory Node“ liefern Hinweise darauf, ob einer der Nodes stets der Engpass ist. Sollte dem so sein, kann man überlegen, die Last bewusster zu verteilen (z. B. bestimmte heavy Prozesse auf eine andere NUMA-Affinität zu legen, oder dem betroffenen Node mehr physikalischen Speicher zu geben falls möglich). Oft lässt sich schon durch Query Tuning oder Aufteilung der Nutzer auf unterschiedliche Knoten eine Besserung erreichen.
- Plan Cache und NUMA: Der Ausführungspläne-Cache von SQL Server ist global, aber intern partitioniert, sodass Zugriffe auf den Cache skaliert werden können. In der Regel muss man hier nichts tun. Es kann jedoch in seltenen Fällen sinnvoll sein, gleichmäßige Parameter für alle Knoten sicherzustellen – zum Beispiel, wenn man OPTIMIZE FOR … Hints nutzt, testen, ob die Pläne auf verschiedenen Knoten ähnlich sind. Normalerweise garantiert der globale Optimizer aber konsistente Pläne.
- Aktuelle Updates einspielen: Gerade bei neuen Versionen wie SQL Server 2022 ist es wichtig, die Cumulative Updates einzuspielen, da Performance-Bugs (auch im NUMA-Umfeld) gefixt werden. Beispielsweise gab es in frühen Builds ein Problem mit Systemen, die mehr als 64 logische Prozessoren pro NUMA-Knoten hatten – ein Update führte hier eine Begrenzung ein, um Stabilitätsprobleme zu vermeiden (SQL Server unterstützt nun bis zu 64 LPs pro Knoten direkt; bei mehr wird Aufteilung notwendig). Solche Änderungen werden in CU-Notizen erwähnt. Es lohnt sich, auf dem aktuellen Patchstand zu sein, um alle Performance-Verbesserungen mitzunehmen.
Letztlich laufen viele SQL Server hervorragend mit Standardeinstellungen auf NUMA-Systemen. Die obigen Best Practices sollen sicherstellen, dass keine suboptimalen Konstellationen vorliegen (wie z. B. falsche VM-Konfiguration oder zu großzügige Parallelität über Knoten hinweg). Indem man diese Punkte beachtet, kann man die Performance auf NUMA-Hardware maximieren und gleichzeitig eine stabile Lastverteilung gewährleisten.
Tools und Diagnostik für NUMA in SQL Server
SQL Server bietet mehrere Möglichkeiten, die NUMA-Konfiguration und -Nutzung zu beobachten und zu diagnostizieren. Hier einige wichtige Tools, Befehle und Sichtweisen:
- SQL Server-Fehlerprotokoll: Direkt nach dem Start der Instanz lohnt ein Blick ins Errorlog. Dort stehen detaillierte Informationen zur erkannten Hardware. Beispielsweise Zeilen, die die Anzahl Sockel, Kerne pro Sockel, logische Prozessoren, sowie Hinweise auf Soft-NUMA enthalten. Man findet auch Meldungen wie „Node configuration: node 0: CPU mask: …“ etc., die beschreiben, welche CPUs welchem Node zugeordnet wurden. Dies ist oft der erste Anhaltspunkt, um zu verifizieren, dass SQL Server die erwartete NUMA-Topologie sieht.
- Dynamic Management Views (DMVs): Es gibt einige DMVs, die explizit NUMA-bezogene Informationen bereitstellen:
- sys.dm_os_sys_info zeigt in Spalten wie numa_node_count und scheduler_count an, wie viele Knoten und Scheduler aktiv sind. Ebenso gibt softnuma_configuration_desc an, ob Soft-NUMA verwendet wird (Werte z. B. „AUTO“ oder „DISABLED“).
- sys.dm_os_nodes listet jeden von SQLOS genutzten Node mit Status (ONLINE/OFFLINE etc.). Hier sieht man z. B., ob ein Node als DAC (dedicated admin connection) reserviert ist oder ob ein Node eventuell aufgrund Hot-Add inaktiv ist. Diese DMV ist in neueren Versionen allerdings nur mit speziellen Rechten abfragbar (ab SQL 2022 benötigt man VIEW SERVER PERFORMANCE STATE).
- sys.dm_os_schedulers ist sehr aufschlussreich: Hier steht pro Scheduler (d.h. pro CPU) der parent_node_id und ob er online ist. Man kann darüber sehen, wie viele Scheduler je Node online sind und sogar die aktuelle Auslastung: Spalten wie load_factor, current_tasks_count, runnable_tasks_count usw. können pro Node aggregiert werden, um die relative Belastung zu vergleichen. Anhand dieser Daten lässt sich diagnostizieren, ob z. B. Node 0 konstant mehr laufende Tasks hat als Node 1.
- sys.dm_os_memory_nodes ist für die Speicheranalyse pro Node zuständig. Sie gibt u.a. aus: memory_node_id (Knoten), wie viel Speicher dort insgesamt zugewiesen ist, und wichtig die Spalten foreign_committed_kb und foreign_target_kb. Diese zeigen, wie viel Speicher ein Node aus fremden Nodes nutzt bzw. an fremde gegeben hat. Wenn hier größere Werte auftauchen, weiß man, dass ein Node „leidet“ und von anderen RAM leihen musste. Außerdem kann man pages_kb sehen, also belegte Seiten pro Node, und free_kb für freien Speicher pro Node innerhalb des Maxima. Damit lässt sich sehr gut überprüfen, ob Speicherprobleme nur einen Knoten betreffen oder das ganze System.
- Weitere DMVs: sys.dm_os_memory_clerks enthält pro Speicher-„Clerk“ (Subsystem) auch die Node-Zuordnung. Damit ließe sich z. B. herausfinden, ob bestimmte Cache-Typen ungleichmäßig auf Nodes verteilt sind. In der Regel braucht man diese Tiefe aber selten. Auch sys.dm_exec_requests zeigt bei laufenden Abfragen an, auf welchem Node ihr Thread gerade läuft (scheduler_id → kann über Join mit schedulers dem Node zugeordnet werden). Solche Analysen sind allerdings eher in Spezialfällen erforderlich (wie Debugging von unbalanced Load).
- Performance Counter – Objekt “SQL Server:Memory Node” und “SQL Server:Buffer Node”: In der Windows-Leistungsüberwachung (perfmon) registriert jede SQL Server-Instanz Zähler pro NUMA-Node. Das Objekt SQL Server:Memory Node hat Instanzen für Node 0, Node 1, etc. und liefert Kennzahlen wie „Database Node Memory (KB)“ – der belegte Speicher mit Datenbankseiten auf dem Node, „Target Node Memory (KB)“ – der gewünschte/ideal Wert (oft ähnlich dem, was max server memory dem Node zuweist), „Foreign Node Memory (KB)“ – der von anderen Nodes stammende belegte Speicher, und „Free Node Memory (KB)“ – freier Puffer auf dem Node. Ähnlich bietet Buffer Node Zähler für Buffer-Hitrate und Seitenreads pro Node. Diese Counter ermöglichen ein Live-Monitoring der Speicheraktivität je Node ohne T-SQL-Abfragen. Insbesondere der Foreign Node Memory Counter ist hilfreich: Steigt er kontinuierlich auf einem Node, ist das ein Zeichen für dauerhaft bereichsübergreifende Speicherzugriffe (evtl. muss man dann mehr Speicher bereitstellen oder Workload umverteilen).
- Task- und Verbindungsanalyse: Mit Abfragen auf sys.dm_os_tasks und sys.dm_exec_sessions lässt sich herausfinden, welche Sessions auf welchen Knoten verbunden sind und wie viele Tasks diese dort gerade haben. Eine mögliche Abfrage: alle laufenden Tasks gruppiert nach parent_node_id und Session-IDs – so sieht man, ob sich z. B. viele Tasks eines bestimmten Users alle auf Node 0 tummeln. Normalerweise sind Sessions aufgeteilt, aber z. B. in dem Szenario mit ungleich verteilten Schedulern (Standard Edition Limit) könnte eine Häufung auffallen. Solche Diagnosen gehen jedoch ins Detail und sind oft nicht nötig, außer bei Verdacht auf Unwuchten.
- XEvent und Wait Stats: Indirekt geben Wartezeiten Hinweise: Der Wait-Typ SOS_SCHEDULER_YIELD könnte öfter auf einem stark ausgelasteten Node auftreten, wenn dort Threads häufig timeouts erhalten. Wartegruppen sind allerdings nicht direkt nach Node getrennt erfasst. Man kann aber mit Extended Events bestimmte Scheduler oder Nodes gezielt beobachten. Beispielsweise ließe sich ein XEvent-Sessions einrichten, die den Scheduler als Action aufzeichnet. Damit könnte man sehen, welche Abfragen auf welchem Node ihre Waits haben. Für den Alltag ist das aber selten erforderlich.
- SQL Server Management Studio (SSMS) Dashboard: In neueren SSMS-Versionen gibt es ein Dashboard (Standardbericht) „CPU-Auslastung“ oder „Laufende Abfragen“, das anzeigt, wie viele Scheduler beschäftigt sind. Diese sind zwar nicht explizit nach Node geordnet dargestellt, aber man kann zumindest erkennen, ob alle vCPUs ähnlich genutzt werden. Besser ist jedoch, die DMVs selbst abzufragen oder ein Monitoring-Tool zu verwenden, das NUMA auswertet (einige Drittanbieter-Monitoringlösungen visualisieren CPU und Memory per Node).
Ein simpler und bewährter Ansatz: Vergleich der Auslastung pro Node. Beispielsweise kann man zyklisch zwei Abfragen laufen lassen – eine für sys.dm_os_schedulers (Anzahl Runnable Tasks pro Node summieren) und eine für sys.dm_os_memory_nodes (freier Speicher pro Node) – und die Ergebnisse loggen. So erkennt man Trends, ob Node 0 immer voll ausgelastet ist und Node 1 Reserven hat oder ob ein Node Speicherstress hat.
Für Troubleshooting auf tiefer Ebene bietet Microsoft zudem einige undocumented DBCC-Befehle (wie DBCC MEMORYSTATUS), die ebenfalls Informationen pro Node liefern (z. B. Anzahl „Foreign Page Entries“). Im Normalfall genügen aber die offiziellen Wege.
Zusammenfassend lässt sich sagen, dass SQL Server viele Einblicke gewährt, um das Verhalten in NUMA-Umgebungen zu verstehen. Als Administrator sollte man regelmäßig einen Blick auf die Node-Verteilung von CPU und Speicher werfen, speziell nach Veränderungen im System (z. B. wenn eine neue Anwendung hinzukommt, die eventuell alle Verbindungen auf einen Knoten konzentriert). Die genannten Tools helfen dabei, Engpässe oder Imbalancen frühzeitig zu erkennen, sodass man gegensteuern kann.
Besonderheiten bei Virtualisierung (SQL Server in VMs auf NUMA-Hosts)
In virtualisierten Umgebungen (VMware, Hyper-V, etc.) spielt NUMA ebenso eine große Rolle – allerdings auf zwei Ebenen: der Host (physischer Server) hat eine NUMA-Architektur, und die virtuelle Maschine kann dem Gastbetriebssystem ebenfalls eine (virtuelle) NUMA-Topologie präsentieren. Für optimale SQL-Server-Performance in VMs sollte man folgende Aspekte beachten:
- vNUMA einschalten: Die meisten Hypervisoren bieten für größere VMs die Möglichkeit, eine virtuelle NUMA (vNUMA)-Topologie an den Gast durchzureichen. Das bedeutet, dass das Gast-OS (z. B. Windows Server in der VM) nicht einfach alle vCPUs als einen einzigen flachen Pool sieht, sondern diese vCPUs in Knoten gruppiert wahrnimmt, analog zur physischen Anordnung. Diese vNUMA-Information ermöglicht dem Gast-OS und somit SQL Server, seine NUMA-Optimierungen im Inneren der VM vorzunehmen, als liefe er auf echter Hardware. Um vNUMA zu nutzen, muss die VM typischerweise mehr als eine gewisse Anzahl vCPUs haben (bei VMware z.B. >8 vCPUs, sofern der Host mehrere Knoten hat) und das Feature darf nicht deaktiviert sein. Wichtig: CPU Hot-Add (dynamisches Hinzufügen von virtuellen CPUs im laufenden Betrieb) ist in vielen Hypervisoren ein Ausschlusskriterium für vNUMA. Wenn CPU Hot-Add aktiviert ist, präsentieren z.B. VMware oder Hyper-V dem Gast aus technischen Gründen keine korrekte NUMA-Topologie, sondern meist einen Node. Daher sollte bei SQL-VMs CPU Hot-Add abgeschaltet werden, damit vNUMA greift.
- Abstimmung von VM-Konfiguration auf Host-NUMA: Ein bewährtes Best Practice ist, die virtuellen Maschinen so zu konfigurieren, dass ihre vCPU-Anzahl und Sockel/Kernel-Aufteilung der physischen Realität möglichst entspricht. Konkret: Wenn der physische Host 2 NUMA-Knoten mit je 10 physischen Kernen hat (also z.B. 2 × 10 = 20 Kerne gesamt), und man möchte eine VM mit 16 vCPUs bereitstellen, dann sollte man diese VM z.B. mit „2 vSockets, 8 vCores pro Socket“ konfigurieren (insgesamt 16 vCPUs). So wird der VM-Gast zwei NUMA-Knoten melden (je 8 vCPUs), was gut zu den 10er-Knoten des Hosts passt. Die Hypervisoren versuchen in dem Fall, die VM intern so zu platzieren, dass jeder virtuelle NUMA-Knoten der VM auf einem eigenen physischen NUMA-Knoten läuft. Umgekehrt wäre eine schlechte Konfiguration: „8 vSockets mit je 2 Kernen“ für dieselbe 16-vCPU-VM. Dann denkt der Gast, er hätte 8 NUMA-Knoten mit je 2 vCPUs – was mit der Host-Realität (2 Knoten à 10 Kerne) gar nicht übereinstimmt. Der Hypervisor müsste diese 8 virtuellen Knoten auf 2 physische abbilden, was bedeutet, dass die VM immer über mehrere virtuelle Knoten verteilt würde, die auf demselben physischen liegen – Chaos und Performanceverlust wären die Folge (z. B. das Gast-OS würde versuchen, 8 Node-spezifische I/O-Threads zu nutzen, obwohl physisch nur 2 Nodes vorhanden sind). Kurz gesagt: Die virtuelle Socket/Core-Einstellung sollte die physische NUMA-Anordnung reflektieren.
- vRAM und NUMA: Nicht nur CPU, auch Speicher in der VM sollte nach Möglichkeit innerhalb eines physikalischen NUMA-Knotens des Hosts bleiben. Beispielsweise: hat der Host 4 NUMA-Knoten mit je 64 GB, wäre es ungünstig, einer einzelnen VM 192 GB RAM zu geben – denn diese 192 GB müssen zwangsweise über 3 Knoten verteilt werden. Der Gast sieht dann (bei vNUMA an) vermutlich 3 virtuelle NUMA-Nodes mit entsprechendem vRAM. SQL Server in der VM wird dann ebenfalls 3 Knoten erkennen. Das funktioniert, ist aber weniger effizient, als wenn die VM kleiner wäre. Natürlich lässt es sich nicht immer vermeiden, größere VMs zu nutzen (Enterprise-Workloads benötigen oft viel RAM und vCPUs). Aber wo möglich, kann man die VM-Größe so wählen, dass sie in einen physischen NUMA-Knoten passt – das liefert meist die beste Performance.
- NUMA-Spanning in Hypervisoren: Manche Hypervisor-Einstellungen erlauben sogenanntes NUMA Spanning (Überspannen). Damit ist gemeint, dass eine VM auf mehrere NUMA-Knoten des Hosts verteilt werden darf. Standardmäßig tun das die meisten, wenn die VM größer ist als ein Knoten. Einige Management-Tools (oder Cloud-Umgebungen) haben Optionen, dies zu steuern. In VMware ist es z.B. automatisch: VMs größer als die Knotengröße werden aufgeteilt. In Hyper-V gibt es die Option “Allow NUMA Spanning” – diese sollte man aktiviert lassen, weil sie es dem Host ermöglicht, VMs überhaupt zu starten, auch wenn sie größer als ein Knoten sind. Gleichzeitig sorgt aber vNUMA (im Gast) dafür, dass der Gast die Aufteilung erkennt. Wichtig ist: vNUMA nicht manuell deaktivieren, außer man hat einen sehr guten Grund.
- SQL Server in der VM richtig konfigurieren: Aus Sicht von SQL Server selbst ändert sich innerhalb der VM nichts – er erkennt die vom Gast-OS gemeldeten NUMA-Knoten und arbeitet wie auf Bare Metal. Als Administrator sollte man allerdings die gleichen Einstellungen wie oben beschrieben anpassen: z. B. MAXDOP orientiert an virtuellen Kernen pro vNUMA-Node, etc. Außerdem ist es ratsam, in der VM keine CPU-Affinität zu nutzen, da der Hypervisor ohnehin selbst die vCPUs managt.
- Beispielumgebung: Angenommen, ein VMware-Host hat 2 NUMA-Knoten à 12 Kerne (24 logisch mit HT pro Node, also 48 LP gesamt). Man möchte eine wichtige SQL Server VM mit 32 vCPUs deployen. Hier sollte die VM beispielsweise mit 2 Sockets × 16 Cores konfiguriert werden. Der Gast (Windows) sieht 2 Nodes à 16 vCPUs. SQL Server darin wird 2 NUMA Nodes mit je 16 Scheduler einrichten. Der Hypervisor wird versuchen, je 16 vCPUs der VM auf je einen Host-Knoten zu legen – so läuft die halbe VM auf physischem Socket 0, die andere Hälfte auf Socket 1. Der SQL Server kann so intern optimal arbeiten, da er genau diese 2-Knoten-Struktur annimmt. Hätte man diese 32 vCPUs VM als 1 Socket × 32 Cores konfiguriert (keine vNUMA für Gast), würde Windows/SQL nur einen Node sehen. Das klingt erstmal einfacher, aber die Realität wäre, dass die 32 vCPUs auf 2 physische Nodes verteilt wären (der Hypervisor muss ja beide Sockel verwenden, um 32 vCPUs zu bedienen). Nun weiß aber der SQL Server nichts davon und behandelt alles uniform. Das Ergebnis wäre deutlich schlechtere Skalierung (weil die Hälfte der Zugriffe remote sind, aber SQL es nicht erkennt). Man würde z.B. sehen, dass ein Node des Hosts überlastet sein kann, während der Gast glaubt, er könne ja noch Threads verteilen – er tappt im Dunkeln. Deshalb: im Zweifel lieber die vNUMA-Struktur vorgeben.
- Achtung bei Standard Edition in VMs: Wie zuvor angesprochen, Standard Edition nutzt max. 4 Sockets. In einer VM bedeutet das: Falls Sie z.B. 8 vSockets à 1 Kern konfiguriert haben, wird Standard nur 4 davon nutzen. Das kann zu seltsamer CPU-Idle-Zeit führen (die VM hat 50% nicht genutzt). Daher VMs mit Standard Edition immer mit ≤4 vSockets konfigurieren (Kerne pro Socket kann man ruhig hoch wählen bis zum Kernlimit). Dann tritt das Problem nicht auf. In unserem Beispiel oben (32 vCPUs) würde Standard Edition ohnehin nur 24 Kerne nutzen können – man würde z.B. 4 Sockets × 6 Cores konfigurieren, damit alle 24 lizenzierten Kerne genutzt werden und es genau 4 Knoten sind.
- Speicherballooning und Dynamische RAM-Zuweisung: Funktionen wie Memory Ballooning oder dynamischer RAM (z.B. Hyper-V Dynamic Memory) können theoretisch dazu führen, dass der tatsächlich einem VM-Knoten zugewiesene physische Speicher sich zur Laufzeit ändert (z.B. etwas Speicher wird von einem anderen Host-Knoten entliehen). Solche Mechanismen sind für DB-Server-Virtualisierung generell nicht empfohlen, da sie Latenz verursachen. Für NUMA heißt das: im Virtualisierungsumfeld möglichst statische Speicherzuteilung für die VM – also fixes RAM, kein Oversubscription, kein Balloning. So bleibt die Zuordnung stabil und SQL Server kann verlässlich mit den Ressourcen pro Node arbeiten.
- Monitoring in VM-Umgebungen: Auch in einer VM kann man innerhalb des SQL Servers wie oben beschrieben DMVs nutzen, um Auslastung je Node zu prüfen. Zusätzlich sollte man aber auf Hypervisor-Ebene im Blick haben, ob die VM wirklich so gemappt ist, wie gedacht. Einige Hypervisor-Tools zeigen die NUMA-Home-Node einer VM oder ob eine VM aufgeteilt ist. Bei Performance-Problemen kann man prüfen, ob ggf. der Hypervisor die VM verschoben hat (vMotion oder Live Migration können z.B. dazu führen, dass eine VM auf einem neuen Host andere NUMA-Eigenschaften hat – eventuell muss dann neugestartet werden, damit vNUMA aktualisiert wird, da sich vNUMA Topologie zur Laufzeit nicht anpasst an neue Hosts).
Insgesamt lässt sich festhalten: Eine gute Abstimmung zwischen VM und Host-NUMA ist entscheidend für SQL Server. Wenn man die VM-Größe und -Einstellungen richtig wählt, kann ein SQL Server in einer VM nahezu genauso effizient laufen wie auf physischer Hardware. Viele Leistungsverluste in virtualisierten SQL-Workloads sind auf falsche vCPU-Aufteilung oder deaktivierte vNUMA zurückzuführen. Wer diese Fallen umgeht, stellt sicher, dass auch in der Cloud oder im Rechenzentrum die Datenbank-VMs die darunterliegende Hardware voll ausnutzen können.
FAQ – Häufige Fragen zu NUMA im Zusammenhang mit SQL Server 2022
-
Frage: Wie kann ich feststellen, wie viele NUMA-Knoten mein SQL Server verwendet?
Antwort: Sie können im SQL Server-Fehlerprotokoll nachschauen – direkt beim Start werden dort die erkannten NUMA-Knoten und CPUs gemeldet. Alternativ liefert die DMV sys.dm_os_sys_info in der Spalte numa_node_count die Anzahl der Knoten. Auch SELECT * FROM sys.dm_os_nodes zeigt alle Knoten (erfordert ggf. spezielle Rechte). -
Frage: Was genau ist ein NUMA-Knoten im Kontext von SQL Server?
Antwort: Ein NUMA-Knoten entspricht einer Gruppe von CPU-Kernen mit zugehörigem lokalem Speicher, die als Einheit verwaltet wird. SQL Server behandelt jeden Hardware-NUMA-Knoten als separaten „Node“ mit eigenen Ressourcen (Scheduler, Memory). In Soft-NUMA-Fällen kann ein Hardware-Knoten in mehrere kleinere Nodes aufgeteilt sein. -
Frage: Was ist der Unterschied zwischen Hard-NUMA und Soft-NUMA bei SQL Server?
Antwort: Hard-NUMA bezieht sich auf die tatsächliche, durch die Hardware vorgegebene Knotenstruktur (physische Sockel und deren Speicher). Soft-NUMA hingegen ist eine vom SQL Server softwareseitig vorgenommene Unterteilung großer Hardware-Knoten in mehrere virtuelle Knoten. Hard-NUMA bestimmt vor allem die Speicherzuordnung (lokaler vs. fremder RAM), während Soft-NUMA nur die Verteilung von CPUs/Threads betrifft, um die Skalierung zu verbessern. Soft-NUMA hat keinen Einfluss auf Speicherlokalität. -
Frage: Unterstützt die SQL Server Standard Edition NUMA?
Antwort: Ja, alle Editionen von SQL Server – inklusive Standard und selbst Express – sind NUMA-fähig und nutzen die NUMA-Optimierungen. Allerdings hat Standard Edition eine Lizenzgrenze von maximal 4 Sockeln oder 24 Kernen. Wenn das System mehr Knoten/Kerne hat, verwendet Standard Edition nur bis zu diesen Limits. Die NUMA-Mechanismen (lokaler Buffer Pool pro Node etc.) funktionieren aber grundsätzlich auch in Standard Edition. -
Frage: Wie soll ich den Max Degree of Parallelism (MAXDOP) auf einem NUMA-System einstellen?
Antwort: Als Faustregel: Setzen Sie MAXDOP auf höchstens die Anzahl der logischen Prozessoren pro NUMA-Knoten. Beispiel: Bei 2 Knoten à 10 Kernen könnten Sie MAXDOP = 10 wählen. So bleiben parallele Abfragen innerhalb eines Knotens. Microsoft empfiehlt zudem generell einen Wert ≤ 8 oder ≤ 16. Die genaue Einstellung kann je nach Workload variieren, aber sie sollte die Knotenstruktur berücksichtigen. -
Frage: Soll ich die CPU-Affinität nutzen, um SQL Server an bestimmte Knoten zu binden?
Antwort: Im Normalfall nein. SQL Server verteilt Last automatisch sinnvoll über alle verfügbaren CPUs. Manuelle CPU-/Knoten-Affinität (über affinity mask) kann zu Unbalanciertheiten führen und wird heute kaum noch empfohlen. Nur in Sonderfällen, etwa wenn man zwei Instanzen auf verschiedenen Knoten strikt trennen will, könnte man Affinitäten setzen. Für einzelne Abfragen gibt es jedoch keine einfache Affinitätssteuerung. -
Frage: Was bedeutet „Foreign Memory“ bzw. „Foreign Pages“ in SQL Server?
Antwort: Das sind Speicherseiten, die ein NUMA-Knoten aus dem Speicher eines anderen Knotens beziehen musste. Wenn Node 0 z. B. keinen freien Puffer mehr hat und eine Seite aus Node 1 RAM bezieht, wird diese Seite als „foreign“ für Node 0 gezählt. Ein hoher Wert an Foreign Pages deutet darauf hin, dass ein Knoten mehr Speicher bräuchte oder die Last sehr ungleich verteilt ist. -
Frage: Wie kann ich überwachen, ob remote Speicherzugriffe meine SQL-Performance beeinflussen?
Antwort: Nutzen Sie die DMV sys.dm_os_memory_nodes und schauen Sie auf foreign_committed_kb – das zeigt an, wie viel Fremd-Speicher jeder Node nutzt. Außerdem bieten Perfmon-Counter im Objekt „SQL Server:Memory Node“ den Wert „Foreign Node Memory (KB)“ je Knoten. Wenn diese Werte stark ansteigen, finden viele remote Zugriffe statt. Zusätzlich kann man die CPU-Auslastung pro Node beobachten: Ungewöhnlich hohe CPU-Wartezeiten auf einem Node könnten auf Remote-Latenzen hindeuten. -
Frage: Warum läuft eine meiner NUMA-Nodes immer heißer (höhere CPU) als die anderen?
Antwort: Mögliche Gründe: Entweder verteilt sich die Workload nicht gleichmäßig – vielleicht nutzen viele aktive Sessions zufällig denselben Knoten – oder eine bestimmte große Abfrage wird immer auf einem Node ausgeführt. Es kann auch sein, dass im Standard Edition-Fall einige CPUs deaktiviert sind und damit ein Node effektiv weniger CPU-Power hat. Um das zu lösen, prüfen Sie die Verbindungsverteilung (Round Robin) und ggf. die Parallelität. Manchmal hilft es, MAXDOP zu begrenzen oder gezielt einige Prozesse mittels Affinität (oder auf separater Instanz) auf den anderen Node zu ziehen. -
Frage: Wie stelle ich sicher, dass beide (alle) NUMA-Knoten meines Servers vom SQL Server auch genutzt werden?
Antwort: Im Allgemeinen kümmert sich SQL Server selbst darum – durch Round-Robin-Zuordnung neuer Verbindungen und parallele Verarbeitung werden alle Knoten einbezogen. Sie können überprüfen, ob beide Nodes aktiv sind: z. B. sollten in sys.dm_os_schedulers auf allen Nodes Scheduler den Status „VISIBLE ONLINE“ haben und etwas Workload (Load_Factor) zeigen. Wenn ein Node komplett Idle bleibt, könnte eine falsche Affinity-Einstellung vorliegen oder (bei VMs) der zweite Node wird dem Gast gar nicht präsentiert. In physischen Systemen ohne spezielle Einstellungen nutzt SQL Server immer alle verfügbaren Nodes automatisch. -
Frage: Kann ich eine einzelne bestimmte Abfrage dazu bringen, nur auf einem bestimmten NUMA-Knoten zu laufen?
Antwort: Direkt gibt es kein T-SQL-Konstrukt wie „EXECUTE ON NODE 1“. Aber indirekt geschieht das sowieso: Eine Abfrage läuft als Thread (oder Threads) und diese sind einer Session zugeordnet, die wiederum einem Node gehört. Wenn Sie also sicherstellen wollen, dass eine bestimmte Abfrage nur z.B. Node 0 nutzt, müssten Sie dafür sorgen, dass die Session dieser Abfrage auf Node 0 landet. In der Praxis lässt sich das nicht ohne Weiteres steuern (SQL verteilt Sessions automatisch). Ein Workaround könnte sein, eine separate Instanz oder ein Resource Governor-Workload auf definierte CPUs zu beschränken. Generell ist das aber selten nötig. -
Frage: Muss ich für NUMA den Speicher manuell nach Knoten aufteilen (z. B. max server memory pro Node festlegen)?
Antwort: Nein, SQL Server verwaltet den zugeteilten Arbeitsspeicher intern pro Node. Sie stellen max server memory für die Instanz insgesamt ein. Die Verteilung auf Nodes geschieht automatisch proportional zur Auslastung/Größe der Nodes. Es gibt keine Einstellung „X GB für Node 0, Y GB für Node 1“ – und das ist auch gut so, denn SQL gleicht das dynamisch zur Laufzeit ab. Nur wenn Sie mehrere Instanzen haben und jede einem Node zuordnen, sollten Sie natürlich jede Instanz mit einem passenden max memory Wert konfigurieren, damit zusammen nicht mehr als der Gesamt-RAM genutzt wird. -
Frage: Was bewirkt die BIOS-Option „Node Interleaving“ in Bezug auf SQL Server?
Antwort: „Node Interleaving“ im BIOS bewirkt, dass der Speicher der NUMA-Knoten nicht mehr aufgeteilt, sondern gleichmäßig über alle Knoten verteilt präsentiert wird – das System verhält sich dann wie ein UMA-System. Für SQL Server (und generell die meisten Anwendungen) verschlechtert dies die Performance, da die Vorteile lokaler Speicherzugriffe verloren gehen, aber die Latenz trotzdem ansteigt. Daher sollte Node Interleaving deaktiviert bleiben (Standardeinstellung in den meisten Server-BIOS), damit das OS die echte NUMA-Topologie sieht. SQL Server läuft auf NUMA hervorragend, solange das BIOS diese nicht versteckt. -
Frage: Hat Hyper-Threading Einfluss auf NUMA und SQL Server?
Antwort: Hyper-Threading (HT) verdoppelt die logischen Prozessoren pro Kern, beeinflusst aber nicht die Einteilung in NUMA-Knoten – beide HT-Threads eines Kerns gehören zum selben Node. SQL Server betrachtet HT-CPUs als separate Scheduler, die jedoch auf die gleichen physischen Ressourcen zugreifen. In Bezug auf NUMA heißt das: ein Node mit 10 Kernen erscheint als 20 Scheduler (bei HT an). SQL Server nutzt sie alle, aber man sollte im Hinterkopf behalten, dass die zweite Hälfte der Scheduler nicht volle zusätzliche Rechenleistung bringt. Bei MAXDOP-Empfehlungen orientiert man sich meistens an physischen Kernen, nicht an den doppelten HT-Threads. Insgesamt verträgt SQL Server Hyper-Threading gut; Latenzen innerhalb eines Nodes können etwas steigen, aber Durchsatz steigt oft. Für NUMA-spezifische Einstellungen muss man nichts ändern – HT wird vom OS gemanagt. -
Frage: Was sind I/O-Completion-Threads und Lazy Writer pro NUMA-Node?
Antwort: Das sind spezielle System-Threads von SQL Server, die pro Knoten vorhanden sind, um wichtige Hintergrundarbeiten knotenlokal zu erledigen. Lazy Writer: Jeder Node hat einen Lazy Writer, der überschüssige Seiten aus dem Buffer Pool dieses Nodes auf Datenträger schreibt, um Platz zu schaffen. So wird das Buffer Management parallelisiert. I/O Completion Thread: Ebenfalls pro Node mindestens einer vorhanden, kümmert sich darum, asynchrone I/O-Vorgänge abzuschließen. Wenn z.B. ein Read von Platte fertig ist, signalisiert das OS dies – der I/O-Thread des entsprechenden Nodes nimmt das Ergebnis entgegen und stellt es dem wartenden Worker bereit. Beide Mechanismen stellen sicher, dass Nodes weitgehend unabhängig arbeiten können, ohne zentralen Flaschenhals. -
Frage: Warum kann die Performance ein und derselben Abfrage auf einem NUMA-System variieren?
Antwort: Ein häufiger Grund ist die Verteilung der Abfrage über die Knoten. Wenn die Abfrage mal mit allen ihre Threads in einem Knoten ausgeführt wird (alles lokal), ein anderes Mal aber Threads auf zwei Knoten landen (weil z.B. der bevorzugte Node gerade ausgelastet war), können die Laufzeiten unterschiedlich sein. Im zweiten Fall müssen die Teilergebnisse zwischen den Nodes ausgetauscht werden, was zusätzliche Latenz bringt. Ebenso kann, falls die Daten mal im lokalen Puffer sind und mal im fremden, ein Unterschied auftreten. Solche Effekte machen die Performance weniger deterministisch. Durch Begrenzung von MAXDOP und gutes Lastmanagement (damit Knoten nicht überlastet/unterlastet wechseln) kann man diese Schwankungen reduzieren. -
Frage: Wo sehe ich, ob SQL Server automatisch Soft-NUMA erstellt hat?
Antwort: Im SQL Server-Fehlerlog finden Sie unmittelbar nach dem Start Meldungen zur Soft-NUMA. Beispielsweise „Automatic soft-NUMA was enabled…“ gefolgt von Auflistungen der Node-Konfiguration. Zudem können Sie SELECT softnuma_configuration_desc FROM sys.dm_os_sys_info ausführen – bei automatischer Aktivierung steht dort „AUTO“. Andernfalls „DISABLED“ oder „MANUAL“ (falls man es manuell gesetzt hätte). Die DMV sys.dm_os_nodes zeigt für Soft-NUMA-Knoten ebenso Einträge (z.B. Node 0,1,2,3 in einem 2-Sockel-System, was bedeutet jeder Sockel in 2 Soft-Nodes geteilt). -
Frage: Sollte man Soft-NUMA manuell konfigurieren oder deaktivieren?
Antwort: In der Regel nein. Die automatische Soft-NUMA-Konfiguration in modernen SQL Server-Versionen ist erprobt und deckt die meisten Szenarien ab. Manuelle Konfiguration via Registry wird nur noch in Ausnahmefällen benutzt – etwa wenn man sehr spezielle CPU-Zuordnungen wünscht oder ältere Versionen ohne Auto-SoftNUMA einsetzt. Deaktivieren würde man Soft-NUMA höchstens zu Testzwecken, wenn man vermutet, dass es ein Problem verursacht (was selten vorkommt). Im Großen und Ganzen: Lassen Sie Soft-NUMA einfach auf der Standardeinstellung. -
Frage: Beeinflusst NUMA irgendwelche SQL Server-Performance Counter?
Antwort: Ja, einige Perfmon-Kategorien sind sogar nach NUMA-Knoten aufgeschlüsselt (Memory Node, Buffer Node, siehe oben). Ansonsten gelten viele Performance Counter pro Instanz als Ganzes, aber Sie sollten beim Interpretieren bedenken, dass diese Werte intern aus den Teilwerten der Nodes bestehen. Beispielsweise „Page Life Expectancy“ (PLE) gibt es global – wenn Sie jedoch auf einem 2-Knoten-System schauen, könnte Node 0 einen viel niedrigeren PLE haben als Node 1; der globale PLE ist dann ein Mittelwert. Ab SQL 2012 wurde PLE sogar pro NUMA-Node in Perfmon bereitgestellt (unter Buffer Node). Kurzum: Für tiefere Analysen lieber die Node-spezifischen Counter nutzen. -
Frage: Muss ich bei TempDB etwas Besonderes bezüglich NUMA beachten?
Antwort: TempDB verhält sich wie jede andere DB im Buffer Pool – d.h. Seiten liegen auf den jeweiligen Nodes, je nachdem wo sie eingelesen/benutzt wurden. Es gibt keine spezielle NUMA-Konfiguration für TempDB. Dennoch: TempDB ist oft ein synchronisierter Hot-Spot (PFS/GAM-Verwaltung). Stellen Sie wie üblich mehrere TempDB-Dateien bereit, um Latches zu entschärfen – das hat aber mit NUMA direkt nichts zu tun. Indirekt könnte man sagen: Wenn eine sehr tempdb-lastige Session immer auf Node 0 läuft, wird Node 0 mehr I/O und Pufferbelastung sehen. Hier greift wieder die allgemeine Lastverteilung – also ggf. schauen, ob diese Workload verteilt werden kann. Aber an TempDB-einstellungen als solches ändert NUMA nichts. -
Frage: Was sollte ich bei SQL Server in einer virtuellen Maschine in Bezug auf NUMA beachten?
Antwort: Wichtig ist, dass die VM vNUMA aktiviert hat und passend zur Host-Hardware konfiguriert ist. Konkret: Deaktivieren Sie CPU-Hot-Add für die VM (damit vNUMA überhaupt wirksam ist). Konfigurieren Sie die Anzahl vSockets und Kerne so, dass es der physischen Aufteilung entspricht (siehe Virtualisierungsabschnitt oben). Innerhalb der VM behandeln Sie SQL Server dann wie physisch: MAXDOP nach virtuellen Kernen pro vNUMA-Node einstellen, etc. Wenn das gegeben ist, verhält sich SQL Server so, als liefe er auf einem kleineren physischen Server – was genau das Ziel ist. -
Frage: Meine SQL-VM hat mehr vCPUs als Kerne in einem Host-NUMA-Knoten. Wie kann ich Performance-Probleme vermeiden?
Antwort: Wenn die VM größer als ein Host-Knoten ist, stellen Sie sicher, dass dem Gast eine entsprechende vNUMA-Struktur präsentiert wird. Beispielsweise Host: 8 Kerne pro Node, VM hat 12 vCPUs – konfigurieren Sie die VM vielleicht als 2 vSockets × 6 Cores. So hat der Gast 2 Nodes. Der Hypervisor wird diese idealerweise auf 2 physische Nodes legen. Damit weiß der SQL Server-Gast von Anfang an, dass es 2 Knoten gibt, und optimiert daraufhin. Ohne diese Konfiguration (wenn Gast denkt es wäre ein Node) würde er intern ineffizient arbeiten, weil er von der Verteilung nichts weiß. -
Frage: Lohnt es sich, auf einem Server mit 2 NUMA-Knoten zwei SQL Server Instanzen laufen zu lassen, jede fest auf einen Node gebunden?
Antwort: Das kommt auf das Szenario an. Wenn Sie zwei getrennte Workloads haben, kann es Sinn ergeben, jede Instanz exklusiv einen Knoten nutzen zu lassen (mittels CPU-Affinität oder indem man je Instanz einer Prozessorgruppe zuweist). So hat jede ihren „eigenen“ RAM und CPU, und sie beeinflussen sich kaum. Allerdings verschenkt man dabei flexible Auslastung – wenn eine Instanz nichts zu tun hat, kann die andere den freien Node nicht nutzen. In vielen Fällen fährt man besser mit einer Instanz, die beide Nodes nutzt, und lässt SQL Server die Ressourcen dynamisch verteilen. Separate Instanzen pro Node sind nur dann ratsam, wenn man klare Isolation will (z. B. unterschiedliche Teams, unterschiedliche Wartungsfenster, oder ein Lizenz-gründiger Split zwischen Anwendungen). -
Frage: Gibt es eine Obergrenze, wie viele NUMA-Nodes SQL Server 2022 handhaben kann?
Antwort: SQL Server kann mit sehr vielen Knoten umgehen – typischerweise weit mehr, als gängige Hardware hergibt (64 Nodes und mehr). Eine wichtige Änderung in SQL Server 2022 betrifft jedoch die Anzahl logischer Prozessoren pro Node: Hier wurde ein Limit von 64 LPs pro NUMA-Node eingeführt (entsprechend der Windows-Prozessorgruppen). Das heißt, wenn ein physischer Knoten mehr als 64 logische CPUs hätte, würde SQL Server nicht starten, bis man die CPU-Konfiguration ändert (bzw. das OS wird in solchen Fällen ohnehin mehrere Prozessorgruppen melden, so dass SQL Server es als mehrere Nodes sieht). In der Praxis stoßen die wenigsten darauf, außer bei sehr großen VM-Hosts oder Spezialhardware. Ansonsten hat SQL Server (Enterprise) keine spezielle Beschränkung der Node-Anzahl außer den physikalischen Grenzen. -
Frage: Muss ich wegen NUMA meine SQL Server-Memory-Einstellungen oder -Programmierung ändern?
Antwort: Im Allgemeinen nicht. Konfigurieren Sie max server memory auf einen sinnvollen Wert für die ganze Instanz, sodass das OS genug übrig behält – das ist unabhängig von NUMA. SQL Server kümmert sich intern um die Verteilung. In der Programmierung (T-SQL) müssen Sie nichts Spezielles berücksichtigen; typische Anfragen ändern sich durch NUMA nicht. Was Sie tun können, ist auf Anwendungsebene ggf. darauf zu achten, dass Ihre Verbindungen gleichmäßig kommen (was SQL aber selbst regelt) und keine einzelne Session gigantische Last erzeugt, die andere Nodes ungenutzt lässt. Im Code braucht man aber keine „NUMA-Abfragen“. Nutzen Sie SQL Server einfach wie gewohnt. Die NUMA-Optimierung läuft hinter den Kulissen für Sie mit.
Weitere Beiträge zum Thema SQL Server
Azure SQL für IT-Entscheider
1. Management Summary Azure SQL bezeichnet eine Familie von Microsofts Cloud-Datenbankdiensten, die SQL Server-Technologie in Azure als Service bereitstellen. Dazu gehören Azure SQL Database (ein einzeldatenbankbasierter PaaS-Dienst für moderne Anwendungen), Azure SQL...
Azure SQL für Entwickler
Management Summary Azure SQL (PaaS) bietet Softwareentwicklern eine fully-managed SQL-Plattform in der Cloud – mit integrierter Hochverfügbarkeit, automatischen Backups und einfacher Skalierbarkeit. Im Vergleich zu einer selbstverwalteten SQL Server-Instanz entfallen...
NUMA, MAXDOP und Co.: Die größten Fehler und Mythen bei der SQL-Server-Konfiguration
Einleitung In der Datenbankadministration von Microsoft SQL Server gibt es eine Reihe von Konfigurationsthemen – insbesondere rund um NUMA (Non-Uniform Memory Access), MAXDOP (Max Degree of Parallelism) und verwandte Einstellungen – bei denen immer wieder typische...
Tutorial: SQL Server-Indizes für Entwickler
Einführung: Dieser Fachartikel richtet sich an Entwickler mit Grundkenntnissen in Microsoft SQL Server und bietet eine umfassende Einführung in das Thema Indizes. Wir beleuchten, was Indizes sind und warum sie für die Performance einer Datenbank entscheidend sind....
Microsoft SQL Server unter Linux – Strategische Analyse und Praxisleitfaden
1. Management Summary Microsofts Entscheidung, SQL Server auch unter Linux anzubieten, markiert einen strategischen Wandel mit weitreichenden Auswirkungen für IT-Entscheider. Erstmals steht damit eine der führenden relationalen Datenbankplattformen...
Blockgröße für SQL Server richtig auswählen (Windows und Linux)
Einleitung: Die Wahl der optimalen Blockgröße (auch Allocation Unit Size oder Dateisystem-Blockgröße genannt) für Datenträger ist ein wichtiger, oft unterschätzter Faktor beim Betrieb von Microsoft SQL Server unter Windows und Linux. Die Blockgröße eines Dateisystems...
Wartungspläne für Microsoft SQL Server
Management Summary Wartung sichert Verfügbarkeit und Datenintegrität: Geplante Wartungsarbeiten in SQL Server zielen darauf ab, die Verfügbarkeit von Datenbanken hoch zu halten und Datenintegrität zu gewährleisten. Sie minimieren Ausfallzeiten und Risiken und...
Virtualisierung von SQL Server, Best Practices
Management Summary Virtualisierung von Microsoft SQL Server ermöglicht es Unternehmen, Datenbank-Workloads effizienter bereitzustellen und zu verwalten. Durch Konsolidierung mehrerer SQL-Server-Instanzen auf weniger Hardware steigern Organisationen die Auslastung und...
SQL Performance-Analyse (hypothetisches Beispiel)
Management Summary Die Performance-Analyse einer Microsoft SQL-Server-Instanz (Version 2019) hat CPU- und I/O-Engpässe als Hauptprobleme identifiziert. In Spitzenzeiten lag die CPU-Auslastung dauerhaft über 90 %, und die Speicher-I/O-Latenz der Datenbanken überschritt...
Indexoptimierung bei SQL Server – Leitfaden für IT-Verantwortliche und DBAs
Einleitung: Wozu dienen Indizes im SQL Server? Indizes sind essenziell, um SQL Server Abfragen zu beschleunigen und die Datenbank-Performance zu verbessern. Ein Index funktioniert ähnlich wie das Inhaltsverzeichnis eines Buches: Anstatt eine Tabelle vollständig zu...