Die Time to First Byte (TTFB) einer Website ist die Zeit von dem Zeitpunkt, an dem der Benutzer mit der Navigation beginnt, bis der erste HTML-Code für die von ihm angeforderte Seite ankommt. Langsame TTFBs sind der Fluch, der über meinem Leben liegt, seit ich vor mehr als zehn Jahren mit WebPageTest angefangen habe.

Es gibt einen Grund dafür, dass TTFB eine der wenigen „Noten“ ist, mit denen WebPageTest eine Website bewertet, und insbesondere, warum es die erste Note in der Liste ist.

Wenn das erste Byte langsam ist, ist auch JEDE andere Metrik langsam. Die Verbesserung von TTFB ist einer der wenigen Fälle, in denen man vorhersagen kann, wie sie sich auf alle anderen Messergebnisse auswirken wird. Jede Millisekunde Verbesserung der TTFB bedeutet eine Millisekunde Einsparung bei jeder anderen Messung (d. h. die Anzeige der ersten Inhalte ist 500 ms schneller, wenn sich die TTFB um 500 ms verbessert). Zu beachten ist, dass eine schnelle TTFB nicht unbedingt ein schnelles Erlebnis garantiert, eine langsame TTFB aber auf jeden Fall für ein langsames Erlebnis sorgt. Ich schätze, dass etwa 50 % aller Hilfeanfragen zu den Ergebnissen von WebPageTest von Website-Besitzern kommen, die mit einer langsamen TTFB kämpfen.

Viele Dinge können in die TTFB einfließen, einschließlich Weiterleitungen, DNS, Verbindungsaufbau, SSL-Verhandlungen und die tatsächliche Server-Antwortzeit. Die meisten davon können relativ einfach mit einem Dienst wie Cloudflare behoben werden, aber die Server-Antwortzeit für das HTML ist oft das größte und am schwersten zu lösende Problem.

Das folgende Wasserfall-Diagramm zeigt als hellblauen Balken die Server-Antwortzeit bei der ersten Anfrage; wenn die Server-Antwortzeit hoch ist, kann das schmerzhaft auffällig sein. Unter optimalen Bedingungen wäre die Server-Antwortzeit nicht länger als der orangefarbene Balken für die Socket-Verbindung direkt davor.

Mehr als drei Sekunden, bis der Server antwortet.

Langsame Antwortzeiten am Ursprung können durch eine Vielzahl von Problemen verursacht werden; diese reichen von der Serverkonfiguration über die Systemlast und die Backend-Datenbanken und -Systeme, mit denen er kommuniziert, bis hin zum Anwendungscode selbst. Um die Performance-Probleme an der Wurzel zu packen, arbeiten in der Regel Teams von DevOps-Ingenieuren mit Application Performance Management-Tools, um die langsamsten Teile der Anwendung aufzuspüren und zu verbessern.

Ein großer Teil der Website-Besitzer, mit denen ich gearbeitet habe, haben nicht die Ressourcen oder das Fachwissen, um diese Art von Untersuchung durchzuführen. Meistens haben sie jemandem eine einmalige Entwicklungsgebühr bezahlt, um ihre Website zu erstellen, oder sie selbst auf WordPress erstellt, und hosten sie bei dem Anbieter mit den niedrigsten Kosten, die sie finden konnten. Das Hosting ist im Allgemeinen so konzipiert, dass es so viele Seiten wie möglich hostet, und nicht unbedingt auf die höchste Performance ausgerichtet.

Edge-Caching von HTML

Die Sache ist die, dass der meiste HTML-Code nicht wirklich dynamisch ist. Sie muss in der Lage sein, sich relativ schnell zu ändern, wenn die Website aktualisiert wird, aber für einen großen Teil des Webs sind die Inhalte über Monate oder Jahre hinweg statisch.

Es gibt spezielle Fälle, z. B. wenn ein Benutzer angemeldet ist (als Administrator oder anderweitig), in denen die Inhalte anders sein müssen, aber die überwiegende Mehrheit der Besuche erfolgt von anonymen Benutzern. Wenn der HTML-Code zwischengespeichert und direkt von der Edge aus bereitgestellt werden kann, können die Performance-Steigerungen erheblich sein (in diesem Fall mehr als 3 Sekunden schneller für alle Metriken).

Sehr viel schnellere Server-Antwortzeit.

Es gibt Dutzende von Plugins für WordPress für das Caching von Inhalten am Ursprung, aber sie müssen konfiguriert werden (wo die Seiten zwischengespeichert werden sollen) und die Performance hängt immer noch stark von der Performance des Hostings selbst ab. Wenn man den Inhalte-Cache weiter zur Edge hin verschiebt, dann nimmt die Komplexität ab, entfällt die zusätzliche Zeit, um zum Ursprung zurückzukehren, und verschwindet die Hosting-Performance vollständig aus der Gleichung. Außerdem kann die Last für die Hosting-Systeme erheblich reduziert werden, weil sie von allen anonymen Zugriffen entlastet werden.

Cloudflare unterstützt das Caching statischen HTML-Codes, und Business- und Enterprise-Kunden können angemeldeten Benutzern ermöglichen, den Cache zu überspringen, indem sie „Cache auf Cookie umgehen“ aktivieren. Das funktioniert gemeinsam mit dem Cloudflare-Plugin für WordPress, sodass der Cache bei jeder Aktualisierung der Inhalte geleert werden kann. Es gibt auch mehrere andere Caching-Plugins, die sich in verschiedene CDNs integrieren lassen, aber in allen Fällen müssen sie mit API-Schlüsseln für das CDN konfiguriert werden, und die Implementierungen sind spezifisch für jedes CDN.

Konfigurationsfreies Edge-Caching von HTML

Für eine breite Anwendung müssen wir das Caching von HTML automatisch (oder so automatisch wie möglich) machen. Zu diesem Zweck benötigen wir eine Möglichkeit, zwischen einem Ursprung (wie einer WordPress-Website) und einem Edge-Cache (wie den Edge-Knoten von Cloudflare) zu kommunizieren, um einen Remote-Cache zu verwalten, der explizit gelöscht werden kann.

Der Ursprung muss Folgendes können:

  • Erkennen, wenn sich vor ihm ein unterstützter Edge-Cache befindet.
  • Angeben, welche Inhalte für welche Besucher zwischengespeichert werden sollen (d. h. Besuche ohne Login-Cookie).
  • Löschen der zwischengespeicherten Inhalte, wenn sie sich geändert haben (global in allen Edges).

Anstatt zu verlangen, dass der Ursprung eine API zum Löschen bei Änderungen einbindet, und manuell konfigurieren zu müssen, was wann zwischengespeichert werden soll, können wir alles mit HTTP-Headern für die Anfragen durchführen, die zwischen den Edges und dem Ursprung hin- und herwandern:

1. Anfragen, die von der Edge zum Ursprung gehen, werden mit einem HTTP-Header versehen, um anzuzeigen, dass ein Edge-Cache vorhanden ist und welche Funktionen er unterstützt:

x-HTML-Edge-Cache: supports=cache|purgeall|bypass-cookies

2. Wenn der Ursprung mit einer zwischenspeicherbaren Seite antwortet, versieht er die Antwort mit einem HTTP-Header mit der Anweisung, dass sie zwischengespeichert werden soll. Der Header enthält auch Regeln, wann die zwischengespeicherte Version nicht verwendet werden soll (damit der Cache für angemeldete Benutzer umgangen werden kann):

x-HTML-Edge-Cache: cache,bypass-cookies=wp-|wordpress

In diesem Fall wird der HTML-Code zwischengespeichert, aber alle Anfragen, die Cookies haben, die mit „wordpress“ oder „wp-“ als Cookie-Namen beginnen, umgehen den Cache und gehen zum Ursprung.

3. Wenn eine Anfrage den Inhalt der Website ändert (Aktualisierung eines Beitrags, Änderung eines Layouts, Hinzufügen eines Kommentars), fügt der Ursprung einen HTTP-Antwort-Header hinzu, der anzeigt, dass der Cache gelöscht werden muss:

x-HTML-Edge-Cache: purgeall

Der einzige knifflige Teil dabei ist, dass die Löschung den Cache in ALLEN Edges bereinigen muss, nicht nur in der einen Edge, die die Anfrage durchlaufen hat.

4. Wenn eine neue Anfrage für HTML-Code eintrifft, der sich im Edge-Cache befindet, werden die Anfrage-Cookies anhand der Regeln für die zwischengespeicherte Antwort überprüft. Wenn die Cookies nicht vorhanden sind, wird die zwischengespeicherte Version bereitgestellt; andernfalls wird die Anfrage an den Ursprung weitergeleitet.

Mit dieser einfachen, Header-basierten Befehls- und Steuerungsschnittstelle besteht keine Notwendigkeit mehr, dass ein Ursprung mit einer API kommunizieren und eine explizite Konfiguration vorgenommen werden muss. So ist es auch deutlich einfacher, die Logik am Ursprung zu implementieren, da es keine Konfiguration (oder Benutzeroberfläche) gibt und keine ausgehenden Anfragen an eine anbieterspezifische API gestellt werden müssen. Das WordPress-Plugin-Beispiel hat weniger als 50 Zeilen Code und die überwiegende Mehrheit davon ist für Rückruffunktionen für alle Ereignisse, die Inhalte ändern.

Noch heute in das Caching mit WordPress und Workers einsteigen

Einer der Aspekte, die ich an Workers am meisten liebe, ist, dass es einem eine voll programmierbare Edge gibt, in der man mit Ideen experimentieren und seine eigene Logik implementieren kann. Ich habe ein Worker-Skript erstellt, das das Header-basierte Protokoll und das Edge-Caching auf den Cloudflare-Edges implementiert, sowie ein WordPress-Plugin, das die Ursprungslogik für WordPress implementiert.

Der einzige knifflige Teil bei dem Worker bestand darin, eine Möglichkeit zu finden, Elemente global aus dem Cache zu löschen. Die Worker-Caches sind jeweils lokal für die Edge und bieten keine Schnittstelle für globale Operationen. Eine Möglichkeit, wie es geht, ist die Verwendung der Cloudflare-API zur Bereinigung des globalen Caches, aber das ist ein ziemlich schweres Geschütz (alles aus dem Cache entfernen, einschließlich Skripte und Bilder), und es erfordert einiges an Konfiguration. Wenn man die spezifischen URLs kennt, die durch eine Inhaltsänderung geändert werden, dann wäre eine gezielte Löschung über die API für genau diese URLs wahrscheinlich die beste Lösung.

Mit der neuen Workers Schlüssel-Werte-Datenbank (Workers KV) können wir den Cache auf eine andere Weise bereinigen. Das Worker-Skript verwendet ein Versionierungsschema für den Cache, bei dem zu jeder URL eine Versionsnummer hinzugefügt wird (d. h. http://www.example.com/?cf_edge_cache_ver=32). Die geänderte URL wird immer nur lokal vom Worker als Schlüssel für die zwischengespeicherten Antworten verwendet und die aktuelle Versionsnummer wird in der Schlüssel-Werte-Datenbank gespeichert, einem globalen Speicher. Wenn der Cache gelöscht wird, wird die Versionsnummer erhöht, wodurch sich die URL für alle Ressourcen ändert. Alte Einträge fallen nach Ablauf ihrer Lebensdauer automatisch aus dem Cache, da auf sie nicht mehr zugegriffen wird. Es etwas Konfiguration erforderlich, um die Schlüssel-Werte-Datenbank für den Worker einzurichten, aber das kann hoffentlich irgendwann in der Zukunft auch automatisch erfolgen.

Was kommt als Nächstes?

Ich denke, die Standardisierung einer Methode, mit der Edge-Caches und Ursprünge für das Caching von dynamischen Inhalten miteinander kommunizieren können, wäre von großem Nutzen für das Web. Es würde Anreize für Content-Management-Systeme schaffen, Unterstützung direkt in die Plattformen einzubauen und eine Standardschnittstelle bereitzustellen, die bei verschiedenen Anbietern genutzt werden kann (und sogar für lokale Edge-Caches in Load Balancern oder anderen Reverse-Proxies). Nach einigen weiteren Tests mit verschiedenen Arten von Websites habe ich vor, das Konzept der IETF HTTP-Arbeitsgruppe vorzulegen, um zu sehen, ob wir einen offiziellen Standard für die Steuerungs-Header (mit unterschiedlichen Namen) entwickeln können. Falls Sie Vorstellungen haben, wie das funktionieren sollte oder welche Funktionen Sie benötigen, würde ich gerne davon hören (z. B. das Löschen für bestimmte URLs, unterschiedliche Inhalte für Mobile/Desktop oder nach Region, die Erweiterung auf alle Inhaltsarten usw.).