- Für den Bau eines Hochleistungs-Webservers wurden bisher verschiedene ereignisbasierte Modelle wie select(), poll(), epoll verwendet.
- Aufgrund der Leistungsgrenzen dieser System-Calls entstand jedoch io_uring, das Anfragen in eine Queue stellt, damit der Kernel sie asynchron verarbeitet.
- kTLS übernimmt die TLS-Verschlüsselung im Kernel, wodurch zusätzliche Optimierungen wie die Nutzbarkeit von sendfile() und Hardware-Offloading möglich werden.
- Mit der Einführung von descriptorless files wird ein für io_uring optimierter Ansatz bereitgestellt, bei dem File-Deskriptoren nicht direkt übergeben werden.
- Das Open-Source-Projekt tarweb, das Rust, io_uring und kTLS kombiniert, zeigt HTTPS ohne zusätzliche System-Calls pro Anfrage und diskutiert auch Fragen zu Sicherheit und Speicherverwaltung.
Die Entwicklung der Architektur von Hochleistungs-Webservern
- Seit den frühen 2000er-Jahren ist der Bedarf an Webservern mit hoher Kapazität gestiegen.
- Anfangs war es üblich, für jede Anfrage einen neuen Prozess zu erzeugen, doch wegen der hohen Kosten entstand dafür die Technik des Preforking.
- Danach entwickelte sich das Feld über die Einführung von Threads und die stärkere Nutzung von select() und poll() weiter, um die Kosten für Context Switches zu senken.
- Allerdings stoßen auch select() und poll() bei vielen Verbindungen an Skalierungsgrenzen, da dem Kernel häufig große Arrays übergeben werden müssen.
Das Aufkommen von epoll
- Unter Linux wurde epoll eingeführt, wodurch sich Mehrfachverbindungen effizienter als mit den bisherigen Verfahren verarbeiten lassen.
- epoll verarbeitet nur Änderungen (Deltas) und reduziert damit unnötigen Ressourcenverbrauch.
- Zwar verschwinden nicht alle System-Calls vollständig, aber die Kosten sinken erheblich.
Überblick über io_uring
- io_uring fügt Anfragen in eine Queue im Speicher ein, statt für jede Anfrage einen System-Call auszuführen, sodass der Kernel sie asynchron bearbeiten kann.
- Legt man zum Beispiel accept() in die Queue, verarbeitet der Kernel den Vorgang und gibt das Ergebnis anschließend in der Completion Queue zurück.
- Der Webserver fügt Anfragen in die Queue ein und prüft die Ergebnisse in einem separaten Speicherbereich.
- Um Busy Loops zu vermeiden, rufen sowohl Webserver als auch Kernel nur dann System-Calls auf, wenn es bei unveränderten Queues wirklich nötig ist, was auch Strom spart.
- Mit passenden Bibliotheken kann ein aktiver Server während der Anfrageverarbeitung ohne zusätzliche System-Calls arbeiten.
Multi-Core- und NUMA-Umgebungen
- Mit Blick auf moderne Multi-Core-CPUs ist eine Strategie sinnvoll, bei der pro Core ein einzelner Thread läuft und die gemeinsame Nutzung von Datenstrukturen minimiert wird.
- In NUMA-Umgebungen lässt sich optimieren, indem jeder Thread nur auf den Speicher seines lokalen Knotens zugreift.
- Für eine perfekt ausgewogene Verteilung von Anfragen ist weitere Forschung nötig.
Speicherallokation
- Sowohl im Kernel als auch im Webserver bleibt Speicherallokation ein Thema, und auch Allokationen im User Space führen letztlich zu System-Calls.
- Auf Webserver-Seite werden im Voraus Speicherblöcke fester Größe pro Verbindung reserviert, um Fragmentierung und Engpässe zu vermeiden.
- Auch im Kernel werden pro Verbindung Ein-/Ausgabepuffer benötigt, die sich zum Teil über Socket-Optionen anpassen lassen.
- Kommt es zu Speichermangel, kann das schwerwiegende Ausfälle verursachen.
Einführung in kTLS (Kernel TLS)
- kTLS ist eine Funktion des Linux-Kernels, die Ver- und Entschlüsselung übernimmt.
- Der Handshake wird in der Anwendung verarbeitet, danach behandelt der Kernel die Datenübertragung so, als wäre es Klartext.
- Dadurch wird die Nutzung von sendfile() möglich, was Speicher-Kopien zwischen User Space und Kernel Space reduziert.
- Wenn die Netzwerkkarte es unterstützt, können sogar die Verschlüsselungsoperationen auf die Hardware ausgelagert werden.
Descriptorless Files
- Dieser Ansatz wurde eingeführt, um den Overhead zu reduzieren, der entsteht, wenn File-Deskriptoren direkt vom User Space an den Kernel Space übergeben werden.
- Mit register_files werden separate „ganzzahlige“ Dateinummern verwendet, die nur innerhalb von io_uring gültig sind und in /proc/pid/fd nicht erscheinen.
- Die ulimit-Beschränkungen des Systems gelten weiterhin.
Vorstellung des Projekts tarweb
- tarweb ist ein beispielhafter Open-Source-Webserver, der all diese Techniken einsetzt.
- Er ist darauf ausgelegt, den Inhalt einer einzelnen tar-Datei bereitzustellen, und kombiniert moderne Hochleistungstechnologien wie Rust, io_uring und kTLS.
- Im praktischen Einsatz gab es Kompatibilitätsprobleme zwischen io_uring und kTLS, etwa fehlende Unterstützung für setsockopt, und einige dieser Probleme wurden per Pull Request behoben.
- Das Projekt ist noch nicht fertig, und die Rust-Bibliothek rustls kann während des Handshakes Speicher allokieren.
- Der Kernpunkt ist, dass HTTPS ohne zusätzliche System-Calls pro Anfrage möglich ist.
Benchmarks und Leistungsmessung
- Der Autor hat bisher noch keine ausreichenden Benchmarks durchgeführt und plant Leistungstests nach einer Überarbeitung des Codes.
Sicherheitsfragen bei io_uring und Rust
- Anders als bei synchronen System-Calls dürfen Speicherpuffer bei io_uring vor dem Completion Event nicht freigegeben werden.
- Das io-uring-Crate garantiert die Compile-Time-Sicherheit von Rust nicht und bietet auch zu wenig Laufzeitprüfungen.
- Bei falscher Nutzung kann es, ähnlich wie in C++, zu schwerwiegenden Problemen kommen, wodurch die eigentliche Sicherheit von Rust geschwächt wird.
- Es wird ein separates safer-ring-Crate benötigt, das Pinning und den Borrow Checker aktiv nutzt.
- Dieses Problem wird bereits in der Community diskutiert.
Referenzen und zusätzliche Links
- Dieser Inhalt bezieht sich auf einen Beitrag, der am 2025-08-22 auf Hacker News diskutiert wurde.
Noch keine Kommentare.