Benchmarking ist ein wesentlicher Bestandteil moderner Softwareentwicklung und Systemoptimierung. Gerade in komplexen Umgebungen wie Datenbanken, Betriebssystemen oder Multicore-Servern dient es nicht nur der Validierung von Verbesserungen, sondern auch dem Erkennen von potenziellen Schwachstellen im System. Doch obwohl Benchmarking auf den ersten Blick eine klare Methode zur Leistungsbeurteilung darstellt, erweist es sich in der Praxis oft als erstaunlich kompliziert und mit vielen Fallstricken verbunden. Ein Beispiel für diese Komplexität findet sich in der Analyse von PostgreSQL-Datenbanken bei Mehrkernprozessoren mit sehr vielen Kernen. Stellen Sie sich eine Maschine mit satten 176 CPU-Kernen vor, die Datenmengen abfragt, die so klein sind, dass sie in den L3-Cache passen.
Die Erwartung wäre, dass die systemweite Leistung mit der Anzahl der parallelen Clients nahezu linear ansteigt. Tatsächlich ist dies bei kleinen Clientzahlen auch der Fall. Doch ab einem gewissen Schwellenwert verhält sich die Performance plötzlich unvorhersehbar und fällt erheblich ab, bevor sie bei noch höheren Clientzahlen wieder ansteigt. Die Ursachen für solch ein Verhalten lassen sich nicht immer auf den ersten Blick erklären. Komplexe Systeme bestehen nicht nur aus Software-Komponenten wie Datenbanken und Anwendungen, sondern auch aus Hardware und Betriebssystemen.
CPU-Architektur, Prozess-Scheduler, Speicherarchitektur und sogar Power-Management-Mechanismen beeinflussen das Ergebnis. So ist es beispielsweise nicht ungewöhnlich, dass einfache Benchmarks wie ein SELECT-Statement auf einen Primärschlüssel innerhalb von PostgreSQL unter bestimmten Umständen weniger performant laufen, als man vermuten würde. Ein naheliegender Verdacht liegt auf Sperrmechanismen (Locking). In Datenbanken können Locks zu Wartezuständen führen, die die Performance mindern. Im konkreten Szenario zeigt sich jedoch, dass Locks kaum eine Rolle spielen.
Die Abfragen sind ausschließlich lesend, sodass keine exklusiven Sperren erforderlich sind. Zudem würde bei zunehmender Clientzahl eine kontinuierliche Verschlechterung der Performance durch steigende Lock-Konkurrenz zu erwarten sein – nicht aber ein plötzliches Fallen und Wiederanstiegen. Auch die Belastung der CPU-Ressourcen scheint nicht der alleinige Grund zu sein. Moderne CPUs verfügen über verschiedene Cache-Ebenen zur Beschleunigung des Datenzugriffs. Hier kann es auftreten, dass bei vielen parallelen Clients der Cache für einzelne Threads zu klein wird, was eine Kettenreaktion bei der Performance auslösen kann.
Doch im betrachteten Beispiel besitzt jede CPU über mehr als 1 GB L3-Cache, was die gesamte Datenmenge inklusive Index in den Cache nimmt. Daher erklärt das Cache-Verhalten den starken Einbruch bei mittleren Clientzahlen nicht adäquat. Ein weiterer Hintergrund für Leistungseinbrüche kann das Power- und Thermomanagement von CPUs sein. CPUs drosseln ihre Taktfrequenz, sobald zu viele Cores gleichzeitig aktiv sind oder Temperaturgrenzen überschritten werden. Dies führt dazu, dass nicht alle Kerne mit maximaler Leistung arbeiten können.
In der untersuchten Situation konnten solche Scheduler-bedingten Drosselungen jedoch ebenfalls ausgeschlossen werden, da Systemmetriken keine plötzlichen Frequenzveränderungen oder thermisch bedingte Maßnahmen erkennen ließen. Ebenso wurde der Einfluss von NUMA-Architekturen (Non-Uniform Memory Access) betrachtet. Diese komplexe Speicherarchitektur teilt das System auf in unterschiedliche Speicher-Knoten, die je nach Prozess und Speicherzugriff unterschiedlich schnell erreichbar sind. Obwohl die Maschine mehrere NUMA-Knoten mit jeweils 44 Kernen besitzt, findet sich keine Verbindung zwischen deren Topologie und dem beobachteten Performance-Phänomen. Die CPU-Zuweisung von Prozessen erfolgt scheinbar zufällig, sodass auch der nicht optimierte Speicherzugriff keine Erklärung für das akute Verhalten liefert.
Spannend wird die Untersuchung der Art und Weise, wie der Betriebssystemkern Prozesse auf CPU-Kerne verteilt. Die Prozess-Scheduling-Struktur kann erhebliche Auswirkungen auf die Effizienz von Kommunikation und Synchronisation zwischen den Prozessen haben. In Datenbankszenarien kommunizieren beispielsweise Backend-Prozesse eng mit Steuerungsprozessen, beispielsweise Benchmark-Clients. Finden diese zweierlei Prozesse auf unterschiedlichen Kernen statt, können Verzögerungen durch Cross-Core-Synchronisation und Prozess-Wakeups entstehen. Diese sorgen dafür, dass kein Kern zu 100 Prozent ausgelastet ist und es zu Leerlaufzeiten kommt.
Experimentell lassen sich verschiedene Pinning-Strategien definieren, bei denen Prozesse entweder gezielt auf denselben Kern gesetzt werden („colocated“) oder zufällig verteilt sind. In Versuchen zeigte sich, dass eine gezielte Ko-Lokation der Prozesse den durchgängigen Leistungseinbruch nahezu aufhebt. Wird dieses Pinning nicht durchgeführt oder die Prozesse sind zufällig verteilt, tritt das Problem in abgeschwächter oder regulärer Form auf. Diese Erkenntnis hebt hervor, wie beeinflusst die Leistung vom Betriebssystem-Scheduling sein kann. Im Rahmen der Untersuchung wurden Kernel-Performance-Counter herangezogen, welche Interrupts, Timer-Wakeup-Events und Kontextwechsel erfassen.
Die Analyse zeigt zwar eine Korrelation zwischen bestimmten Ereignissen und der Performance, aber es fehlt eine schlüssige Erklärung, warum gerade bei etwa 22 Clients die Leistung einbricht und bei 100 Clients wieder ansteigt. Ein direktes Muster ist bei den Schaltvorgängen nicht zu erkennen, was die Komplexität des Problems nochmals unterstreicht. Sperrmechanismen wurden anfänglich ausgeschlossen, doch später zeigt sich eine leichte Leistungsverbesserung bei bestimmten Patches, die sich mit dem Index-Root-Page-Locking befassen. Dabei handelt es sich allerdings um Optimierungen, die maximal einen Teilaspekt adressieren. Das grundlegende Problem mit dem Durchsatz-Einbruch wird dadurch nicht beseitigt.
Interessanterweise verschiebt sich die Grenze, bei der Leistungsverbesserungen einsetzen, mit den Patches zu niedrigeren Client-Zahlen, was auf einen komplexeren Zusammenhang zwischen Lock-Verhalten und Scheduler-Performance schließen lässt. Die Frage, ob der Linux-Kernel (hier Version 6.11 versus 6.15) oder bestimmte Kernel-Features das Verhalten verursachen, wurde ebenfalls geprüft. Einige Optimierungen oder Deaktivierungen von Features wie NUMA-Balancing, Autogroup-Scheduling oder transparenten großen Seiten brachten keine signifikanten Änderungen.
Auch der Einsatz von FreeBSD mit identischer Hardware führte zu ähnlichen Ergebnissen, was auf eine plattformübergreifende Natur des Problems hinweist. Eine weitere spannende Beobachtung betrifft die Tatsache, wie ausgelastet das System während des Benchmarks ist. Überraschenderweise führte eine gezielte Auslastung der CPU mit niedrig priorisierten Stress-Threads zu einer deutlichen Steigerung der Datenbank-Transaktionen pro Sekunde. Dieses Phänomen lässt sich durch die Steuerung von CPU-Leerlaufzuständen erklären – wenn eine CPU zu oft in tiefe IDLE-Modi versetzt wird, benötigt sie erhebliche Zeit zum „Aufwachen“, was effektive Leistung bremst. Durch die konstante Auslastung bleibt der Prozessor „wach“ und kann dadurch effizienter arbeiten.
Das Bild wird komplettiert, wenn man die Pipeline-Technik betrachtet, bei der mehrere Abfragen in einer Pipeline gesendet werden, ohne auf das Ergebnis des Vorgängers zu warten. Pipeline-Workloads wirken sich ebenfalls positiv aus, indem sie die Kerne kontinuierlich beschäftigen und so verhindern, dass die CPU in ineffiziente Leerlaufphasen verfällt. Mit zunehmender Pipeline-Größe verschwindet der beobachtete Leistungseinbruch nahezu vollständig. Um die Hardwareabhängigkeit besser zu verstehen, wurden Tests auf verschiedenen Maschinen mit unterschiedlichen CPUs durchgeführt. Maschinen mit weniger Kernen oder älteren Intel-Prozessoren zeigen ähnliche, wenn auch nicht identische, Skalierungs- und Durchsatzmuster.
Auch hier sind Phasen mit geringerer Skalierung und plötzlich ansteigendem Durchsatz zu beobachten. Dieses Verhalten scheint damit weder spezifisch für eine CPU-Architektur noch für ein Betriebssystem zu sein. Zusammenfassend lässt sich sagen, dass Benchmarking weit über die reine Messung hinausgeht. Es erfordert ein tiefes Verständnis der beteiligten Hard- und Softwarekomponenten sowie der Wechselwirkungen zwischen ihnen. Kleine Änderungen in der Prozessplatzierung, CPU-Auslastung oder Locking-Strategien können große Auswirkung auf die vermeintlich klaren Performance-Messungen haben.