Die Optimierung der I/O-Latenzermittlung bei extrem hohen Eingabe-/Ausgabeoperationen pro Sekunde (IOPS) stellt eine wesentliche Herausforderung im Bereich moderner Systemüberwachung dar, besonders auf Servern mit Hunderten von CPUs. Die effiziente Erfassung und Analyse von Block-I/O-Latenzen ist entscheidend, um Leistungseinbußen durch Monitoring-Overhead zu vermeiden und gleichzeitig die Systemstabilität zu gewährleisten. Ein besonders leistungsfähiges Werkzeug zur dynamischen Analyse auf Linux-Systemen ist eBPF (extended Berkeley Packet Filter). Um die Vorteile von eBPF für die Latenzmessung nutzbar zu machen, bedarf es jedoch gezielter Optimierungen vor allem bei extrem hohen Lastszenarien wie 37 Millionen IOPS auf einer Maschine mit 384 CPUs. In dieser Analyse wird ein neuer Ansatz vorgestellt, der den Monitoring-Overhead drastisch reduziert und die Skalierbarkeit bei hohen Parallelitätsgraden signifikant verbessert.
Die angestrebte Umgebung basiert auf einem Dual-Socket AMD EPYC Genoa Server, bestückt mit 21 NVMe-SSDs, darunter sowohl PCIe5- als auch PCIe4-Geräte, welcher theoretisch durch seine hohe Bandbreite und CPU-Leistung bis zu 38,5 Millionen IOPS erreichen kann. Standardmäßig wird für die Latenzermittlung das Tool biolatency aus dem iovisor/bcc-Toolset verwendet. Allerdings zeigte sich bei der Nutzung von biolatency unter Vollast ein drastischer Durchsatzverlust und signifikante CPU-Belastung, was den Monitoringansatz ohne Optimierungen für den produktiven Einsatz unbrauchbar macht. Die Hauptursache dieser Limitierung liegt in der herkömmlichen Arbeitsweise von biolatency bzw. der darunterliegenden eBPF-Probestruktur.
Biolatency benutzt primär zwei Tracepoints im Kernel, block_rq_issue und block_rq_complete, um für jede I/O-Anforderung Start- und Endzeit zu protokollieren. Dabei wird die Startzeit in einer globalen Hashmap mit dem Zeiger der Request-Struktur als Schlüssel gespeichert und bei der Fertigstellung zum Berechnen der Latenz wieder abgerufen. Dieses Vorgehen ist auf Systemen mit maximalen 20 Millionen derartigen Operationen in der Sekunde problematisch, da es enorme Latzenz- und CPU-Overheads durch häufige Hashmap-Operationen hervorruft. Besonders die global sichtbare Speicherung macht die Datenstruktur zu einem Engpass, da konkurrierende Zugriffe auf mehreren Hundert CPUs zu Cache-Kohärenz-Verzögerungen führen. Zusätzlich erfordert die Aktualisierung der Histogrammwerte, welche die Latenzklassen sammeln, atomare Operationen über die CPUs hinweg, was die Skalierbarkeit weiter einschränkt.
Die Herausforderung bei der Optimierung ist also zum einen das Eliminieren der hochfrequenten und starken Synchronisation nötigenden Hashmap-Operationen. Zum anderen sind eine geringere Zahl an Tracepoints und die Verwendung von per-CPU-datenstrukturen sinnvoll, um Synchronisationskosten zu reduzieren. Die innovative Lösung besteht darin, den Startzeit-Hashmap-Zwang zu umgehen, indem direkt auf die in der Kernelstruktur struct request bereits eingetragenen Startzeitstempel zugegriffen wird. Diese Startzeitinformationen wurden im Linux-Kernel seit Version 2.6.
35 standardisiert implementiert und ermöglichen eine genaue Erfassung des I/O-Startzeitpunkts ohne zusätzliche eBPF-Inserts oder Lookup-Operationen. Damit ist es möglich, den Tracepoint block_rq_issue komplett auszuschalten und sich ausschließlich auf block_rq_complete zu fokussieren. In diesem einzigen Tracepoint kann dann der Unterschied zwischen Start- und Endzeit der I/O-Operation berechnet werden, wodurch der bisher doppelte eBPF-Overhead pro I/O auf nur einen Bruchteil reduziert wird. Gleichzeitig wurde die globale Histogramm-Hashmap in eine BPF_MAP_TYPE_PERCPU_HASH umgewandelt. Dies bedeutet, dass jede CPU eine eigene lokale Kopie der Latenzhistogramme führt.
Veränderungen an lokalen Datenstrukturen benötigen keine teuren Synchronisationsmechanismen und verringern so den Cache- und CPU-Verkehr erheblich. Erst beim Auslesen der Ergebnisse werden diese per-CPU-Daten zusammengeführt, was den Aufwand zu einem Zeitintervall bündelt und nicht laufend stört. Diese Änderungen führen zu beeindruckenden Verbesserungen in der Praxis: Die durchschnittliche eBPF-Probezeit im block_rq_complete-Tracepoint sinkt auf nur noch wenige hundert Nanosekunden, während zuvor einige Mikrosekunden anfallen. Der CPU-Verbrauch für das gesamte Monitoring reduziert sich um das 59-fache, sodass statt hunderten CPUs nur rund zehn CPUs für das Monitoring aktiv sind. Daraus ergibt sich eine drastisch reduzierte Latenz und ein deutlich gesteigerter Durchsatz für Nutzlasten, ohne auf wichtige Monitoringfunktionalitäten verzichten zu müssen.
Die Reduktion der Anzahl der Tracepoints von zwei auf nur einen minimiert zudem den Context-Switch- und Performancemechanismus-Verlust im Kernel. Eine weitere positive Nebenauswirkung ist, dass die Speichergröße des verwendeten Hashmaps deutlich verringert werden kann. Während zuvor aufgrund der globalen Sichtbarkeit eine sehr große Kapazität notwendig war, um den hohen Durchsatz abzudecken, reicht nun eine kleinere Hashmap mit geringerem Speicherverbrauch. Dadurch wird der eBPF-Code speichereffizienter und schneller in der Ausführung. Trotz dieser Vorteile sind noch einige Herausforderungen zu meistern.
So ist die Latenzermittlung mit nur dem block_rq_complete-Tracepoint weniger granular, etwa die exakte Erfassung des ursächlichen Prozesses (PID) bleibt derzeit schwierig, da das struct request im Kernel keine PID speichert. Deswegen kann die Überwachung von individuellen I/O-Quellen eingeschränkt sein, was für bestimmte Anwendungsfälle die Interpretation erschwert. Zudem wurden während der Optimierung gelegentlich unerwartete Latenzspitzen und längere Verzögerungen beim Aufräumen der per-CPU-Datenstrukturen beobachtet. Diese scheinen mit dem Linux-eBPF-Subsystem bei sehr hoher Parallelität zusammenzuhängen und erfordern weitere Untersuchungen und mögliche Kernel-Verbesserungen. Die entstandene neue biolatency-Variante ist aktuell als Beta-Version verfügbar.
Sie zeigt das enorme Potenzial von eBPF für hochleistungsfähige Systemüberwachung unter extremen Lasten und bietet eine Grundlage für zukünftige Weiterentwicklungen im Bereich I/O-Performance-Monitoring. Die Herangehensweise dient als Referenz für den Umgang mit eBPF-Programmen auf massiv parallelen Systemen und demonstriert, wie tiefes Fragmentwissen des Betriebssystems und seiner Datenstrukturen genutzt werden kann, um Monitoringwerkzeuge fit für die moderne Hardware zu machen. In Summe zeigt diese Optimierung, wie wichtig es ist, Monitoringmethoden eng an die Systemeigenschaften und vorhandene Kernel-Funktionalitäten anzupassen, um Performanceeinbußen selbst bei höchster Systemauslastung zu minimieren. Auf Systeme mit mehreren hundert CPUs und Millionen IOPS lässt sich durch gezielte Softwarearchitektur hohe Präzision bei gleichzeitiger Effizienz im Monitoring erzielen. Anwender mit extrem leistungsstarken Serverumgebungen und hohem I/O-Aufkommen sollten die vorgestellte Methode ausprobieren und durch die Integration neuer Linux Kernelfähigkeiten und eBPF-Mechanismen langfristig von schnellerer, genauerer und ressourcenschonender Block-I/O-Latenzstatistik profitieren.
Die Zukunft der Systemanalyse und Fehlersuche auf Hochleistungs-Computersystemen wird maßgeblich von solchen innovativen eBPF-Optimierungen geprägt sein. Abschließend ist zu erwarten, dass durch die Einbindung solcher effizienten Techniken sowie fortschreitender Kernelverbesserungen das Monitoring von I/O-Latenzen selbst bei Milliarden Operationen pro Sekunde realisierbar wird, ohne die produktive Leistung empfindlich zu beeinträchtigen.