QUIC soll als neuer Standard das weit verbreitete TCP-Protokoll ablösen. Welche Neuerungen QUIC mitbringt, wo seine Stärken liegen und warum überhaupt eine Weiterentwicklung von TCP benötigt wird, versuche ich in diesem Artikel zu beantworten.
QUIC allgemein
Um zu verstehen, was QUIC ist und was es verbessern möchte, muss man zuerst einen Blick auf die heute verwendeten Protokolle werfen.
In den meisten Fällen stellt man heute eine Anfrage über das HTTP/2 Protokoll, das auf dem Transportprotokoll TCP basiert. Dieses wird als Basis verwendet, da es sich um ein robustes und zuverlässiges Protokoll handelt. Wenn ein TCP-Paket seinen Empfänger nicht erreicht, wird das verloren gegangene Paket erneut angefordert. Darüber hinaus garantiert TCP, dass die empfangenen Pakete in der selben Reihenfolge verarbeitet werden, in der sie versendet wurden.
HTTP/2 und TCP sind Protokolle, die keine Verschlüsselung implementiert haben. Damit trotzdem verschlüsselt übertragen werden kann, wird im Fall von HTTPS zusätzlich TLS zur Verschlüsselung eingesetzt. [4]
QUIC ist dahingegen ein Protokoll, dass nicht mehr auf TCP sondern auf UDP aufbaut. UDP ist im Vergleich zu TCP nicht robust und zuverlässig. Der Ansatz von UDP entspricht eher dem “‘Fire and Forget”‘ Ansatz. [1] Geht ein UDP Paket beispielsweise auf dem Weg zum Empfänger verloren, wird dieses nicht erneut angefordert. Auch die Reihenfolge der empfangenen Pakete ist irrelevant.
UDP hat sich vor allem im Bereich des Streamings durchgesetzt. Wenn innerhalb eines Frames für einen einzelnen Pixel nicht alle Informationen empfangen wurden, ist dies nicht schlimm, da das menschliche Auge diesen Fehler mit recht hoher Wahrscheinlichkeit gar nicht erst wahr nimmt.
Darüber hinaus besitzt UDP keinen Verbindungs-Handshake, wie das bei TCP der Fall ist (3-Way-Handshake).
Da QUIC der Nachfolger von TCP werden soll, kann man sich an dieser Stelle berechtigterweise die Frage stellen, warum UDP als Basis verwendet wird. Es wäre auf den ersten Blick sicherlich logischer das TCP Protokoll weiterzuentwickeln. Dies führt aber zu einem entscheidenden Nachteil.
TCP liegt genauso, wie UDP und alle darunterliegenden Protokolle, wie beispielsweise IP, im sogenannten Kernel-Space. Dieser ist ein Teil des Betriebssystems. Beim Ändern der im Kernel-Space implementierten Protokolle muss daher der Kernel und damit das gesamte Betriebssystem, aktualisiert werden. Bei Millionen an Middle-Boxes, die unsere Anfragen durch das Internet routen, würde es Jahre dauern die teilweise heute schon stark veralteten Middle-Boxes zu aktualisieren. [1][2]
QUIC ist im sogenannten User-Space implementiert. Das bedeutet, dass QUIC auf Anwendungsebene, wie beispielsweise in einem Browser, implementiert ist. Bei Änderungen des Protokolls muss daher auch nicht der Kernel-Space angepasst werden.
Einerseits hat dies den Vorteil, dass nicht erst alle Middle-Boxes aktualisiert werden müsse. UDP ist bereits ein weitverbreitetes Protokoll und wird flächendeckend unterstützt. Auf der anderen Seite können Änderungen deutlich schneller ausgespielt werden, wenn dafür zum Beispiel nur die Aktualisierung des Browsers notwendig ist. Tests werden hierdurch auch vereinfacht. [1][2]
Damit QUIC dieselbe Robustheit und Zuverlässigkeit wie TCP erreicht, müssen die entsprechende Algorithmen in QUIC implementiert werden. Aus diesem Grund kann auch das unzuverlässigere aber flexiblere Protokoll UDP als Basis verwendet werden.
Zusammenfassend kann man sagen, dass QUIC die Schnelligkeit von UDP mit der Robustheit und der Zuverlässigkeit von TCP vereinen möchte. [2]
Eine weiter Änderung, die sich durch die Verwendung von QUIC und damit auch bei der Verwendung von HTTP/3 ergibt, ist die standardmäßige Verschlüsselung. Bei den vorherigen HTTP-Versionen (HTTP 1.0, HTTP 1.1 und HTTP/2) musste TLS als zusätzliches Protokoll zur Verschlüsselung eingebunden werden.
Da QUIC selbst schon Algorithmen beinhaltet, die Multiplexing (Zusammenfassen) von Verbindungen erlaubt, muss eine neue HTTP Version (HTTP/3) entwickelt werden. Diese hat einen geringeren Funktionsumfang als HTTP/2, da neben dem Multiplexing von Verbindungen auch bereits Funktionalität zum Verbindungsmanagement in QUIC implementiert ist. [2]
Initialer Verbindungsaufbau
Wenn man mit HTTP/2 eine Webseite aufrufen möchte, muss zunächst die TCP-Verbindung aufgebaut werden. Dabei wird der sogenannte 3-Way-Handshake des TCP-Protokolls ausgeführt. Im ersten Schritt sendet der Client eine Anfrage (SYN) an den Server. Wenn dieser die Verbindung annimmt, antwortet er dem Client (SYN+ACK), dass er bereit ist Daten zu empfangen. Der Client muss daraufhin dem Server bestätigen, dass er dessen Antwort erhalten hat. Wenn diese Nachricht beim Server ankommt, ist der 3-Way-Handshake abgeschlossen und Nachrichten können über TCP ausgetauscht werden.
Nach dem Aufbauen der TCP Verbindung muss zwischen dem Client und dem Server noch die Verschlüsselung mit TLS aufgebaut werden. Der Aufbau benötigt genauso, wie der Verbindungsaufbau von TCP, drei Nachrichten. [3][4]
An dieser Stelle befindet sich bereits ein Problem. Das eigentliche Anliegen, also die HTTP-Anfrage an den Server wird erst nach diesem initialen Verbindungsaufbau an den Server gesendet. Umso mehr Pakete beim initialen Verbindungsaufbau übertragen werden müssen, desto länger dauert es, bis die eigentliche Anfrage beantwortet wird.
Dies führt vor allem dann zu Problemen, wenn ein Server sehr weit von einem Client entfernt ist. Durch die große Entfernung kann die Latenz der Verbindung sehr hoch sein, was zu langen Übertragungszeiten führt. Verbunden mit der großen Anzahl an Paketen, die für den initialen Verbindungsaufbau benötigt werden, können Anfragen sehr lange dauern. Mit weniger Paketen, die übertragen werden müssen, könnte die Anfrage schneller beantwortet werden. [1]
An diesem Problem setzt QUIC an.
Initial besitzt UDP keinen Verbindungsaufbau. Um QUIC jedoch zuverlässiger zu machen, gibt es an dieser Stelle auch eine Art 3-Way-Handshake. Die Besonderheit an dieser Stelle ist jedoch, dass im Rahmen dieses Handshakes auch die Verschlüsselung aufgebaut wird. Nach dem Verbindungsaufbau kann dann die eigentliche HTTP-Anfrage übertragen werden.
Unter HTTP/2 wird im Vergleich dazu die TLS-Verschlüsselung erst nach dem Einrichten der Verbindung aufgebaut. [4]
Insgesamt bedeutet die Umstellung auf QUIC eine Halbierung der Paket, die für den initialen Verbindungsaufbau notwendig sind. Damit verbunden dauert der Handshake in der Theorie auch nur halb so lange.
HTTP
Ein weiterer Grund für die Einführung von QUIC ist das sogenannte Head-of-Line Blocking. Dieses ist am Besten zu verstehen, wenn man die Entwicklung des HTTP Protokolls näher betrachtet.
HTTP 1.0
Bei der Verwendung von HTTP 1.0 muss für jeden Request eine neue eigene Verbindung aufgebaut werden. Das bedeutet, dass zwei Anfragen an den selben Server jeweils ihre eigene HTTP-Verbindung aufbauen müssen. Dies beinhaltet pro Verbindung einen eigenen TCP- und TLS-Handshake. [4]
HTTP 1.1
Da ein HTTP-Verbindungsaufbau durch seine Dauer ein recht “‘teures”‘ Vorhaben ist, wurde mit HTTP 1.1 die Idee verfolgt, Verbindungen wiederzuverwenden. Bei der Verwendung von HTTP 1.1 werden daher alle Anfragen an den selben Server über eine TCP-Verbindung gesendet. Dabei werden die Anfragen sequenziell an den Server gesendet.
Dies bedeutet jedoch, dass man Ressourcen nur nacheinander anfragen kann, was in einem Performancenachteil resultiert. Beim Aufrufen einer Website werden oftmals mehrere Ressourcen benötigt und angefragt. Tests haben ergeben, dass das parallele Anfragen dieser, trotz des mehrfachen Verbindungsaufbaus schneller ist, als das sequenzielle Anfragen der Daten über eine TCP-Verbindung. Aus diesem Grund wird das Feature kaum eingesetzt. [4]
HTTP/2
Mit HTTP/2 wurde dann Multiplexing eingeführt. Multiplexing ist das Zusammenfassen mehrerer Verbindungen zu einer Verbindung. Der Unterschied zu den mit HTTP 1.1 eingeführten Änderungen ist, dass nun parallele Anfragen über eine Verbindung möglich sind. Die Ressourcen müssen nicht mehr sequenziell angefragt werden. Das heißt, dass nur noch eine Verbindung aufgebaut werden muss, über welche Anfragen gestellt werden können. Die Besonderheit an dieser Stelle ist, dass nicht mehr auf Ergebnisse vorheriger Anfragen gewartet werden muss.
Durch das Multiplexing ergibt sich jedoch nun ein neues Problem: Head-of-Line Blocking. [4]
Head-of-Line Blocking
Beim Head-of-Line Blocking handelt es sich um das Blockieren der Verarbeitung bereits empfangener Pakete, durch den Verlust eines vorherigen Pakets.
Ein HTTP/2-Server stellt bei einer Anfrage, die Ressourcen bereit und zerteilt diese in einzelne TCP Pakete. Die TCP Pakete werden daraufhin zum Client zurückgesendet. Dabei kann es sein, dass das erste übermittelte Paket von Ressource-A stammt und das zweite übermittelte Paket zu einer ganz anderen Ressource gehört. Wenn der Server die Antwort-Pakete nun verschickt, kann es dazu kommen, dass irgendeine Middlebox im Internet eines der Pakete fallen lässt. Die anderen Pakete werden ganz normal an den Empfänger übermittelt. [4]
Das TCP-Protokoll garantiert, dass die empfangenen Pakete in der Reihenfolge verarbeitet werden, in der sie versendet wurden. Im Fall des verloren gegangenen Pakets kann genau dies zum Problem werden. Bevor bereits empfangene Pakete verarbeitet werden können, die erst nach dem verloren Paket versandt wurden, muss das verlorene Paket nachgefordert werden. Dies blockiert die gesamte Verarbeitung der nachfolgenden Pakete, auch wenn diese komplett unabhängige Daten beinhalten. Von der Unabhängigkeit weiß TCP jedoch gar nichts, da das Multiplexing Teil des HTTP/2 Protokolls ist. [4]
QUIC – kein Head-of-Line Blocking
Bei QUIC tritt das Head-of-Line Blocking nicht mehr auf. Dies liegt an der Verwendung von UDP. Auf der einen Seite besitzt UDP keinen Mechanismus um verlorene Pakete erneut anzufordern. Auf der anderen Seite spielt es bei der Verwendung von UDP-Paketen keine Rolle, in welcher Reihenfolge die Pakete verarbeitet werden.
Wenn ein Paket verloren gehen sollte, können bereits empfangene Pakete auf QUIC-Ebene schon weiterverarbeitet werden. Separat hiervon kann das verloren gegangene Paket nun von QUIC neu angefordert werden. [4]
Performancetests
Interessant ist im nächsten Schritt, ob diese theoretischen Überlegungen in der Praxis tatsächlich Performance-Verbesserungen hervorrufen. Aus diesem Grund wurden unter anderem von Cloudflare einige Performancetests durchgeführt.
Handshake
QUIC halbiert die Anzahl der Nachrichten, die für den initialen Verbindungsaufbau notwendig sind. Um zu messen, welcher Verbindungsaufbau schneller ist wurde die “time to first byte”, also die Zeit bis das erste Byte übertragen wird verglichen. Das Aufbauen der HTTP/2 Verbindung hat dabei im Schnitt 201 ms gedauert. Der HTTP/3-Verbindungsaufbau war im Vergleich dazu mit 176 ms im Durchschnitt 12,4 % schneller. [6]
Synthetische Performancetests
Danach wurde die Performance der Nachrichtenübertragung mit synthetischen Performancetests ermittelt. Dabei misst man die Übertragungsgeschwindigkeit der Paketen mit einer festen Paketgröße. Der erste Test umfasste HTTP/2 und HTTP/3 Pakete mit einer Größe von 15 KB.
Im Durchschnitt ist HTTP/3 mit QUIC bei diesem Test 15ms schneller als HTTP/2 mit TCP. [6]
Der nächste Test umfasste Nachrichten mit einer Größe von 1 MB. Dabei stellte sich heraus, dass HTTP/2 mit TCP besser performt als HTTP/3 mit QUIC. Im Durchschnitt wurden die Nachrichten in diesem Test mit HTTP/2 und TCP 27ms schneller empfangen als bei HTTP/3. [6]
Bei Nachrichten mit einer Nachrichtengröße von 5 MB verstärkt sich dieser Trend. Auch hier ist das Übermitteln der Nachrichten mit HTTP/3 und QUIC langsamer als unter HTTP/2 mit TCP. Der Unterschied liegt in diesem Fall im Durchschnitt sogar bei 126ms.
Daran zeigt sich, dass die Performance bei der Verwendung von QUIC bei immer größeren Nachrichten, im Vergleich zu TCP immer schlechter wird. [6]
Real-World Performancetests
Neben den synthetischen Performancetests wurden auch “Real-World” Performancetests durchgeführt. Dabei wurde der Cloudflare-Blog (blog.cloudflare.com) auf einen HTTP/3 Server deployed. Die Tests wurden dann mit Hilfe des WebPageTest-Frameworks durchgeführt. In Kombination mit dem hauseigenen Monitoring-Tool “Browser Insights” wurden Metriken gesammelt um Aufschluss über die Performance der Seite zu geben.
Auch in diesem Test schneidet HTTP/2 im Durchschnitt besser ab als HTTP/3. Der Unterschied liegt hier bei circa 1-4%. [6]
Insgesamt schneidet HTTP/2 mit TCP in allen Tests, bis auf den reinen Handshake-Vergleich besser ab. Je größer die zu übermittelnden Nachrichten dabei werden, desto besser sind die Übertragungszeiten von HTTP/2 im Vergleich zu HTTP/3.
Tests von Google zeigten darüber hinaus, dass die CPU-Last der Server durch die Verwendung von QUIC doppelt so hoch, wie bei der Verwendung von TCP in Kombination mit TLS ist. Dies bedeutet, dass Google doppelt so viel CPU-Ressourcen zur Verfügung stellen müsste, um die selbe Anzahl an Anfragen verarbeiten zu können. [5]
Auswertung
Die Cloudflare-Ingenieure vermuten, dass es aufgrund der verwendeten Congestion Algorithmen zu den Unterschieden in der Performance kommt. Bei Congestion Algorithmen handelt es sich um Algorithmen, die bei Überlastung eines Servers ankommende Pakete fallen lassen. [5][6]
Die Congestion Algorithmen, die auf TCP-Ebene verwendet werden, sind bereits über viele Jahr optimiert worden. Auf Ebene von QUIC hat diese Optimierung noch nicht stattgefunden. Aus diesem Grund vermuten die Cloudflare-Ingenieure, dass die verwendeten Congestion Algorithmen den theoretischen Performancevorteil (bspw. kein Head-of-Line Blocking) von QUIC wieder zunichte machen. [6]
Die erhöhte CPU-Last bei der Verwendung von QUIC kann an zwei Dingen liegen. Die erste Möglichkeit sind wieder die noch nicht optimierten Congestion Algorithmen. Auf der anderen Seite kann dies daran liegen, dass QUIC im Userspace verarbeitet wird. Dadurch ist Kommunikation zwischen den User- und dem Kernel-Space notwendig, die die CPU weiter belasten kann. TCP wird komplett im Kernel-Space verarbeitet und hat dieses Problem dadurch nicht. [5]
Fazit
Abschließend kann man sagen, dass QUIC in der Theorie viele Probleme von TCP lösen kann. Gerade in Kombination mit HTTP sind hier Performancevorteile zu erwarten.
In der Praxis zeigt sich jedoch, dass TCP und HTTP/2 zur Zeit, vor allem bei größeren Nachrichten besser performen als QUIC und HTTP/3. Bei kleinen Nachrichten und beim Verbindungsaufbau sind HTTP/3 und QUIC bereits schneller.
Dies ist damit zu erklären, dass für QUIC noch viele Optimierungen notwendig sind. So muss beispielsweise das HTTP/3 Protokoll noch auf QUIC angepasst werden. Die Congestion Algorithmen sind darüber hinaus auch noch nicht optimiert.
Man sieht das QUIC damit im Vergleich zu TCP noch am Anfang steht. Trotzdem setzen viele der großen Tech-Firmen bereits auf das neue Protokoll und stellen ihre Dienste auch über HTTP/3 zur Verfügung. Daher kann man davon ausgehen, dass weiterhin an der Optimierung von QUIC geforscht wird und dass QUIC immer häufiger Verwendung finden wird.
QUIC und HTTP/3 selber ausprobieren
Um auszuprobieren, welche Dienste bereits HTTP/3 unterstützen, benötigt man einen entsprechenden Client. Für die Browser Firefox und Chrome gibt es dafür jeweils den Nightly-Build:
Neben den klassischen Browsern gibt es auch eine Vielzahl an Bibliotheken, die bereits QUIC unterstützen. Hier sind einige:
- aioquic (Python)
- Haskell QUIC
- kwik (Java)
- Isquic (C)
- mvfst (C++)
- Neqo (Rust)
Viele der großen Tech-Firmen setzten bereits HTTP/3 ein, mit denen man HTTP/3 selber erleben kann. Hier sind einige Beispiele aufgelistet:
Author: Max Merz — merzmax.de, @MrMaxMerz
Quellen
[1] M. Geniar, Google’s QUIC protocol: Moving the web from TCP to UDP
[2] A. Ghedini, Accelerating UDP packet transmission for QUIC | The Cloudflare Blog
[3] A. Ghedini, The road to QUIC | The Cloudflare Blog
[4] A. Ghedini und R. Lalkaka, HTTP/3: The past, the present, and the future | The Cloudflare Blog
[5] R. Marx, QUIC and HTTP/3 : Too big to fail?! | Web Performance Calendar
[6] S. Tellakula, Comparing HTTP/3 vs. HTTP/2 performance | The Cloudflare Blog
Leave a Reply
You must be logged in to post a comment.