Datenbank: PGO-Vorteile schwinden unter höherer Last in Percona-Tests
Die Leistungsgewinne der relationalen Open-Source-Datenbank durch profilgesteuerte Optimierung schwanden in Tests, über die Marco Tusa von Percona berichtete, mit zunehmender Konkurrenz zwischen Datenbankaktivitäten. Eine mit eng begrenzten Trainingsläufen erstellte Version schnitt sogar schlechter ab als die Vergleichsversion. Seine Ergebnisse verdeutlichen eine praktische Grenze der Compiler-Optimierung: Häufig genutzten Code schneller zu machen, beseitigt nicht zwangsläufig die Engpässe, die bei einer stark ausgelasteten Datenbank entscheidend sind.
Die profilgesteuerte Optimierung (PGO) nutzt Messwerte aus Trainingsläufen, um einen Compiler zu steuern – also die Software, die Quellcode in ausführbare Programme umwandelt. Eine mit Messcode versehene Programmversion zeichnet auf, welche Funktionen und Ausführungspfade am häufigsten durchlaufen werden; eine anschließend erstellte Version nutzt diese Aufzeichnungen, um die Optimierung zu priorisieren. Im dritten Teil seiner Reihe zu PGO untersucht Tusa, warum das Training der Datenbank mit sysbench-tpcc, einem Benchmark für die Transaktionsverarbeitung, ein enttäuschendes Ergebnis lieferte und ob ein breiter angelegtes Training helfen könnte.
Die gemeldeten Leistungsgewinne hängen stark von der Arbeitslast ab. Tusa nennt in seinen Schlussfolgerungen eine Verbesserung von 12 % bei geringer Nebenläufigkeit, also relativ wenigen gleichzeitig ablaufenden Aktivitäten, aber weniger als 2 % bei stärkerer Ressourcenkonkurrenz, wenn diese Aktivitäten um gemeinsam genutzte Ressourcen konkurrieren. An anderer Stelle beschreibt er die Gewinne als unter 5 % und hält es für plausibel, dass der zusätzliche Nutzen einer besseren Abdeckung nebenläufiger Abläufe unter 1 % liegen könnte. Diese letzte Zahl ist eine Erwartung, kein separat ermittelter Messwert. Die Zahlen beschreiben unterschiedliche Vergleiche und sollten nicht als eine allgemeingültige Leistungssteigerung dargestellt werden.
Warum eng begrenztes Training die Datenbank langsamer machen kann
Tusa benennt drei Probleme, die sich gegenseitig verstärken. Erstens beansprucht sysbench-tpcc nur einen Teil des Serverprogramms der Datenbank. In seiner Erörterung des GCC-Compilers werden Funktionen ohne aufgezeichnete Ausführungen als „kalt“ eingestuft: Der Compiler bevorzugt kleineren Code, vermeidet Inlining – das direkte Einfügen des Codes einer Funktion in die aufrufende Funktion – und verschiebt ihn in seltener genutzte Bereiche. Eine laufende Datenbank führt jedoch weiterhin Bereinigungsarbeiten zum Entfernen veralteter Datenversionen, das Schreiben geänderter Daten auf den Datenträger, die Neuberechnung von Statistiken, Fehlerbehandlung, alternative Ausführungspläne für Abfragen sowie wiederholten Verbindungsaufbau und -abbau aus.
Diese Vorgänge können daher weniger auf Geschwindigkeit optimierten Code durchlaufen. Der Wechsel zwischen häufig und selten genutzten Bereichen kann auch den Instruktionscache beeinträchtigen, den schnellen Speicher des Prozessors für ausführbare Anweisungen. Tusa verweist auf die GCC-Option partial-profile-training als Schutz davor, Code zu benachteiligen, den das Training nicht abgedeckt hat.
Zweitens berichtet er von unzuverlässigen Zählwerten beim Training mit 128 bis 1024 Threads, also den parallelen Ausführungseinheiten, die Datenbankaktivitäten abarbeiten. Die standardmäßigen Profilzähler von GCC sind in der von ihm beschriebenen Konfiguration nicht atomar: Gleichzeitige Aktualisierungen werden nicht zwingend als unteilbare Vorgänge erfasst. Aktualisierungen können verloren gehen und Zählwerte inkonsistent werden. Beim Kompilieren mit aktivierter Profilnutzung erhielt Tusa Warnungen über beschädigte oder inkonsistente Profildaten und bezeichnet atomare Profilaktualisierungen als notwendige Schutzmaßnahme. Seine mit sysbench trainierte PGO-Version war bei 128 Threads langsamer als eine herkömmliche, mit Optimierungsstufe 3 erstellte Version, die eine hohe Compiler-Optimierungsstufe nutzt.
Drittens: Die Messung verändert das gemessene Verhalten. Laut Tusa verlangsamt die Instrumentierung die Ausführung um den Faktor 2 bis 10. Dadurch verändert sich, an welchen Stellen sich Threads bei Ressourcenkonkurrenz stauen. Mutexe und Lese-Schreib-Sperren, also Mechanismen zur Koordination des Zugriffs auf gemeinsam genutzte Daten, können sehr viel Zeit in aktiven Warteschleifen verbringen, die wiederholt prüfen, ob ein Zugriff möglich ist. Ihre enormen Ausführungszahlen können den Compiler dazu veranlassen, das Warten statt der produktiven Arbeit zu optimieren. Das Umordnen von Codeverzweigungen kann weder die zugrunde liegende Konkurrenz um Sperren noch Verzögerungen beim Speicherzugriff beseitigen.
Breite Abdeckung mit nebenläufigen Aktivitäten kombinieren
Tusas Alternative kombiniert den database test runner (MTR), das Testwerkzeug der Datenbank, mit einer sysbench-Arbeitslast bei moderater Nebenläufigkeit. MTR deckt ein breites Funktionsspektrum ab: das Interpretieren von Abfragen, die Auswahl von Ausführungsplänen, Änderungen an Datenbankstrukturen, Replikation – das Kopieren von Änderungen zwischen Datenbankservern – und Fehlerbehandlung. Laut Tusa laufen seine Arbeitslasten jedoch fast ausschließlich über einzelne Verbindungen, wodurch wichtige nebenläufige Ausführungspfade unterrepräsentiert bleiben.
Zu diesen Pfaden gehören Latches, um die konkurriert wird, also leichtgewichtige Koordinationsmechanismen; die Mehrversionen-Nebenläufigkeitskontrolle (MVCC), die bestimmt, welche Datenversionen eine Transaktion sehen kann, während andere schreiben; Warteschlangen für das Warten auf Sperren; sowie Group Commit, das Transaktionsabschlüsse über das zur Wiederherstellung verwendete Redo-Log bündelt. Tusa schlägt etwa 4 bis 64 sysbench-Threads vor, um diese Pfade zu durchlaufen, ohne die starken Verzerrungen sehr hoher Nebenläufigkeit zu verursachen. Zählwerte größer als null helfen zu verhindern, dass der Compiler diese Pfade als ungenutzt einstuft.
Die Unterscheidung zwischen den Compilern ist wichtig. Tusa erklärt, dass die GCC-Laufzeitumgebung Zählwerte aufsummieren kann, wenn aufeinanderfolgende Trainingsläufe dieselbe instrumentierte Programmdatei und dasselbe Profilverzeichnis verwenden. Beim Beenden des Programms werden die Werte zu den vorhandenen Dateien hinzugefügt. Allerdings verwenden die veröffentlichten Build-Befehle Clang 20, nicht GCC. In seinem demonstrierten Ablauf werden Rohprofile ausdrücklich mit Version 20 des LLVM-Werkzeugs für Profildaten im Sparse-Merge-Modus zusammengeführt. Das Ergebnis wird in der Profildatendatei der Datenbank im Profilverzeichnis des Quellcodebaums unterhalb des Verzeichnisses für optionale Software abgelegt. Die GCC-Schutzmaßnahmen dürfen nicht mit Optionen verwechselt werden, die in diesen Clang-Befehlen als aktiviert gezeigt werden.
Was das demonstrierte Experiment umfasst
Die instrumentierte Version verwendet den Datenbankserver in Version 9.7.2, die Release-Konfiguration, die C- und C++-Compiler von Clang in Version 20, den Linker lld, der kompilierte Programmkomponenten zusammenfügt, sowie eine aktivierte Profilerzeugung. Die Link-Time-Optimierung (LTO), also die Optimierung über kompilierte Komponenten hinweg beim Linken, ist deaktiviert. Das Training führt MTR mit acht parallelen Test-Workern für die Testsuiten der Bereiche Hauptfunktionen, transaktionale Speicher-Engine, Undo der transaktionalen Speicher-Engine, Binärlog, Replikation, Performance Schema und Systemvariablen aus. Auch bei fehlgeschlagenen Tests wird fortgefahren.
Der anschließende Lauf von sysbench-tpcc verwendet 64 Threads für 600 Sekunden, 10 Tabellen und einen Skalierungsfaktor von 100. Er wählt Daten gleichverteilt aus, nutzt die Transaktionsisolationsstufe Repeatable Read zur Steuerung dessen, welche Änderungen Transaktionen sehen können, aktiviert die Bereinigung und deaktiviert Prepared Statements, also wiederverwendbare, vorverarbeitete Datenbankanfragen. Die Verbindungen bleiben geöffnet, statt wiederholt neu aufgebaut zu werden. Diese Einstellungen beschreiben die Trainingslast, keine empfohlene Produktionskonfiguration.
Tusas Profilzusammenfassung nennt 52.403 Funktionen, 619.050 Codeblöcke und einen Gesamtzählwert von 2.473.216.025.729. Der höchste Zählwert einer Funktion beträgt 289.910.820.864, der höchste Zählwert eines internen Blocks 16.881.288.068. Nur zwei Blöcke machen 20 % der aufgezeichneten Zählwerte aus; 2.165 Blöcke beziehungsweise 0,35 % machen 90 % aus. Bei breiterer Betrachtung entfallen 99,9999 % auf 220.001 Blöcke beziehungsweise 35,54 %.
Er deutet diesen konzentrierten Kern zusammen mit einer breiten Masse selten ausgeführter Blöcke als Beleg dafür, dass sowohl die anhaltende sysbench-Aktivität als auch die funktionale Abdeckung durch MTR beigetragen haben.