Optimierung von VMs durch NUMA

Einführung CPU & NUMA Architektur

Die Hauptaufgabe einer jeden CPU ist es Daten abzuarbeiten. Hier ist aber der Irrglaube, desto schneller die CPU (oder desto mehr CPUs ich zuordne), desto schneller werden die Daten abgearbeitet. Ganz so einfach ist das leider nicht, denn bevor die CPU die Daten abarbeiten kann, müssen diese vom langsameren System RAM ausgelesen werden und dies kann zu einer Latenz in der CPU führen. Ein erster Schritt der Hersteller um die Wartezeit auf die zu lesenden Daten zu minimieren, ist es einen internen Speicher Cache (lokaler RAM) an die CPU anzubinden, auf den der Zugriff um bis zu 95 % schneller ist.

Optimierung von VMs durch NUMA

Wenn die CPU aus dem lokalen RAM liest, wird dies über einen Datenbus transferiert, der von allen CPUs eines Systems gemeinsam genutzt wird. Wenn die Anzahl der CPUs in einem System zunimmt, steigt auch der Datenverkehr entlang dieses Busses, und die CPUs können am Ende, beim Zugriff auf den lokalen RAM, in Konflikt geraten. Hier kommt NUMA ins Spiel – NUMA wurde entwickelt, um das Problem der Systembus-Konflikte zu minimieren, indem die Anzahl der Pfade zwischen CPUs und dem lokalen RAM erhöht wird.

NUMA (Non Uniform Memory Architecture) zerlegt ein System in Knoten von CPUs und einem direkt angebundenen lokalem RAM. NUMA-Knoten sind so optimiert, dass die CPUs eines Knoten, vorzugsweise den lokalen RAM innerhalb dieses Knotens verwenden. Das Vorteil hierbei ist, dass CPUs normalerweise nur mit anderen CPUs innerhalb ihres NUMA-Knotens um den Zugriff auf ihren lokalen RAM und nicht mit allen CPUs in einem System konkurrieren.

Ein Beispiel hierfür ist ein System mit 4 Sockets, mit jeweils 4 Kernen, und 128 GB RAM. Ohne NUMA sind dies 16 physische Prozessoren (pCPU), die möglicherweise auf demselben Systembus in eine Warteschlange gestellt werden, um auf 128 GB RAM zuzugreifen. Wenn dasselbe System in 4 NUMA-Knoten aufgeteilt würde, hätte jeder Knoten 4 CPUs mit lokalem Zugriff auf 32 GB RAM.

16 pCPU / 4 NUMA-Knoten = 4 pCPU pro NUMA-Knoten

128 GB RAM / 4 NUMA-Knoten = 32 GB RAM pro NUMA-Knoten

NUMA

NUMA in virtuellen Umgebungen

Die NUMA-Knoten Größe sollte berücksichtigt werden, wenn große Server-Workloads wie Exchange, SQL, Citrix Worker usw. virtualisiert werden. Zu dem Thema NUMA Awareness gibt es mehrere Studien der verschiedenen Hersteller, zum Beispiel im Zusammenhang mit XenApp, konnte durch NUMA Awareness die Anwenderdichte um ca. 25% erhöht werden.

Es gibt folgende Regeln im Zusammenhang mit NUMA in virtuellen Umgebungen:

  1. Die Anzahl der virtuellen CPUs (vCPU) einer VM sollte kleiner oder gleich der Anzahl der physikalischen CPUs (pCPU) des NUMA-Knoten sein.
    Der Hypervisor weist die VM einem Home-NUMA-Knoten zu, wo der lokale RAM und pCPU des NUMA-Knoten bevorzugt verwendet wird. Best Practices in diesem Fall ist, dass der zugewiesene RAM kleiner als der lokale RAM des NUMA-Knotens ist.
  2. Die Anzahl der vCPUs einer VM ist größer als die Anzahl der pCPUs im NUMA-Knoten (“Wide VMs”).
    Wide-VMs werden in mehrere NUMA-Clients aufgeteilt, wobei jedem Client ein anderer Home-NUMA-Knoten zugewiesen wird. Wenn eine VM, auf einem Hypervisor mit 4 pCPUs pro NUMA-Knoten erstellt wird und diese beispielsweise mit 8 vCPUs konfiguriert wurde, muss der Hypervisor die VM in 2 NUMA-Clients mit je 4 pCPUs unterteilen. Diese 2 NUMA-Clients sind jeweils 2 verschiedenen Home-NUMA-Knoten zugeordnet. Das Problem beim Aufteilen einer Wide-VM in mehrere NUMA-Clients besteht darin, dass die Möglichkeit besteht, dass einer der NUMA-Clients auf den lokalen Speicher eines anderen NUMA-Knoten zugreifen muss.
NUMA in virtuellen Umgebungen

Oben schrieb ich „NUMA-Knoten Größe“ – was habe ich damit gemeint?  Ob Sie es glauben oder nicht, alle Intel-Chips sind nicht gleich erstellt. Und nicht alle Sockets haben nur einen (1) zugrunde liegenden NUMA-Knoten. Also ist die offensichtliche Frage, welche NUMA-Konfiguration für die gekaufte Hardware zugrunde liegt. Hierfür gibt es Tools wie Coreinfo, für Windows-Betriebssysteme, oder Befehle, die im Hypervisor ausgeführt werden. 

xl info -l
oder 
numactl -hardware

Wenn die CLI nicht gefällt, können diese Informationen auch direkt über den Hardware Hersteller (Whitepaper) eingeholt werden. Aus Erfahrung kann ich jedoch sagen, dass ältere Sockets fast immer in mehrere NUMA-Knoten aufgeteilt werden. Neuere Intel-Chips zum Beispiel, sind weniger oder gar nicht aufgeteilt. Dies ist einer der Gründe, warum früher immer 2 vCPUs (virtuelle CPUs) für XenApp Worker empfohlen wurden

2 Sockets mit 4 Kernen = 8 pCPU
8 pCPU / 4 NUMA-Knoten = 2 pCPU pro NUMA-Knoten

An der oberen Rechnung sieht man, das dies die Optimale Größe war, um NUMA Trashing (Zugriff auf NUMA fremde Ressourcen) zu unterbinden. Auf neuerer Hardware sind weniger NUMA Knoten pro Socket verbaut, so dass sich der Sweet Spot (4-8 vCPU) nach oben verschoben hat.

2 Sockets mit 4 Kernen = 8 pCPU
8 pCPU / 1 NUMA-Knoten = 8 pCPU pro NUMA-Knoten

Um nun das Sizing eines Citrix Workers richtig zu berechnen, muss auch noch die CPU-Over-Subscription betrachtet werden. Auch wenn ich vor ein paar Jahren immer mit einer 1,5-fachen-Quote beim CPU-Over-Subscription gerechnet habe, musste ich dies bei der moderneren Hardware (seit ca. 2-3 Jahren) anpassen. Daher ist meine neue Quote seit geraumer Zeit, eine 2-fache Over-Subscription-Quote. Durch mehrere interne Testreihen mit LoginVSI und echten Workloads in Produktiv Umgebungen konnte ich feststellen, dass dies der optimale Sweet-Spot in Bezug auf Benutzerdichte pro Host ist.

Hier nun ein Beispiel zum Sizing einer Windows Server 2016 VDA Worker Umgebung mit allen wichtigen Faktoren. Die Hypervisor Hosts sind mit 2 Sockets, mit je 20 Kernen und der NUMA-Knoten Größe 2 ausgestattet (Hyper-Threading aktiv).

2 Sockets * 20 Kerne = 40 pCPU

40 pCPU / 2 NUMA-Knoten = 20 pCPU pro NUMA-Knoten

20 pCPU * 2.0 CPU Over-Subscription = 40 vCPU

40 vCPU / 5 Worker = 8 vCPU pro Worker

5 Worker * 2 NUMA-Knoten = 10 Worker pro Host

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.