2 Punkte von GN⁺ 2025-08-23 | Noch keine Kommentare. | Auf WhatsApp teilen
  • 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.

Noch keine Kommentare.