Sechs Sekunden bis zur Startseite

Manchmal merkt man erst, wie langsam eine Website geworden ist, wenn man sie selbst aufruft und plötzlich Zeit hat, sich einen Kaffee zu holen.

Genau so ging es mir mit meiner Seite hier: Die Startseite brauchte plötzlich bis zu sechs Sekunden, bis überhaupt etwas passierte. Um mich nicht falsch zu verstehen: bis der erste HTML-Response kam. Danach liefen CSS, JavaScript und Bilder völlig normal durch. Das Problem lag also ziemlich offensichtlich vor dem Browser, irgendwo auf dem Server.

Sechs Sekunden sind nicht nur für einen Androiden, sondern generell im Web eine Ewigkeit. Also habe ich mir Chatty dazugeholt, um mir beim Debuggen zu helfen.

Die Ausgangslage war schnell klar: Der erste Request auf hnz.io/ hing. Danach ging alles zügig weiter. Das spricht meistens dafür, dass der Server beim Rendern der Seite lange beschäftigt ist.

Mein erster Reflex war die klassische Performance-Lösung: Caching.

Der erste Versuch: Staticache

Kirby hat von Haus aus schon ein Caching-Modul an Board, ich habe mich aber zusätzlich noch für Staticache entschieden. Das berechnet im ebsten Fall eine Seite bis zur nächsten Änderung einmal vor und gibt dann die Vorberrechnete Version an die Browser weiter.

Zusätzlich habe ich die gzip-Komprimierung eingeschaltet. Gzip ist eine Technik, mit der Textantworten wie HTML oder CSS kleiner gemacht werden, bevor sie übertragen werden. Das spart Bandbreite und beschleunigt die Übertragung.

Der erste Test brachte allerdings eine kleine Überraschung: Die Startseite brauchte immer noch mehrere Sekunden.

Wenn Caching nicht hilft

Das war der Punkt, an dem klar wurde, dass der Cache gar nicht das eigentliche Problem löst.

Wenn eine Seite trotz Cache mehrere Sekunden braucht, dann passiert eines von zwei Dingen:

  • Entweder der Request landet gar nicht im Cache.
  • Oder der Request muss trotzdem noch viel dynamische Logik ausführen.

Raten bringt an dieser Stelle nichts. Also haben Chatty und ich angefangen zu messen.

Ein kleiner Performance-Schalter

Ich wollte eine möglichst leichte Lösung, etwas, das direkt auf dem Server mitläuft und nur dann aktiv ist, wenn ich es brauche und habe ich einen kleinen Schalter eingebaut.

Wenn man die Seite mit ?perf=1 aufruft und eingeloggt ist, schreibt die Anwendung Performance-Daten in ein Log. Zusätzlich erscheinen Timing-Informationen im HTTP-Header, die ich dann im Browser anzeigen kann.

Gemessen wurden drei Dinge:

  • Bootstrap: der Start von PHP und Kirby
  • Renderzeit: das eigentliche Erzeugen der HTML-Seite
  • Gesamtzeit: alles zusammen

Zusätzlich habe ich kleine Marker eingebaut, mit denen man bestimmte Stellen im Code messen kann. Wenn ein Snippet oder eine Funktion auffällig langsam ist, taucht sie im Log sofort auf.

Das Ganze ist im Grunde ein sehr einfacher Profiler. Nicht besonders elegant, aber extrem hilfreich.

Die Spur führt ins Rendering

Die Messungen zeigten ziemlich eindeutig, wo die Zeit verloren ging und zwar, und das hat mir ungemein geholfen, nich im Netzwerk, DNS oder im TLS-Handshakke. Die ganze Zeit ging für das PHP-Rendering der Seite drauf: Der Server war mehrere Sekunden damit beschäftigt, die Startseite zu berechnen, bevor überhaupt etwas an den Browser geschickt wurde.

Damit war klar, dass wir uns meine Templates und Snippets anschauen müssen. War ja irgendwie auch klar.

In Kirby sind Snippets kleine wiederverwendbare Template-Blöcke. Man kann sie sich wie Komponenten vorstellen, die HTML generieren.

Viele kleine Verzögerungen

Die Logs zeigten ein interessantes Muster: Der heftigste Block war die Liste der Beiträge auf der Startseite. Das Snippet, das die Feed-Karten erzeugt, brauchte mehrere Sekunden.

Innerhalb dieses Blocks tauchten immer wieder ähnliche Zeitwerte auf. Viele einzelne Schritte lagen jeweils bei ungefähr 300 Millisekunden.

Das klingt erstmal nicht dramatisch, aber wenn ein Feed zehn Beiträge hat und jeder Beitrag mehrere solcher Schritte ausführt, summiert sich das schnell auf mehrere Sekunden.

Ein Beispiel waren Social-Links aus dem IndieConnector. Dabei handelt es sich um Funktionen, die automatisch prüfen, ob ein Beitrag auf Mastodon oder Bluesky veröffentlicht wurde und welche URL dazu gehört. Die hatte ich mal auf die Schnelle eingebaut. Und nun ja.

Man sollte nichts auf die Schnelle einbauen.

Niemals.

Zwar ist die Auflösung auf einer Detailseite völlig okay. Im Feed bedeutet sie aber, dass dieselben Informationen mehrfach berechnet werden.

Dazu kamen kleine Dinge wie Bild-Fallbacks oder Kommentarzählungen, die ebenfalls für jeden Beitrag erneut ausgeführt wurden. Teilweise mehrfach.

Der entscheidende Schritt

Die Lösung bestand letztlich darin, zwei verschiedene Kontexte sauber zu trennen: Die Startseite ist ein Listing. Sie zeigt viele Beiträge gleichzeitig. In diesem Kontext muss alles möglichst leichtgewichtig sein.

Eine Detailseite dagegen zeigt nur einen einzigen Beitrag. Dort kann man sich mehr Rechenzeit leisten.

Also habe ich einige Funktionen im Feed bewusst abgeschaltet oder vereinfacht.

  • Social-Links werden im Listing nicht mehr komplett aufgelöst.
  • Kommentarzahlen greifen auf bereits berechnete Metriken zurück.
  • Einige Bildlogiken wurden reduziert.

Die Detailseiten behalten weiterhin die vollständige Funktionalität.

Das Ergebnis ist eine klare Trennung: Der Feed bleibt schnell, während Detailseiten alle Informationen anzeigen.

Und Chatty hat dabei geholfen, die kritischen Stellen zu identifizieren und die Messungen sauber auszuwerten.

Ein kleiner Nebeneffekt

Während wir ohnehin am Feed gearbeitet haben, habe ich auch die Bildgrößen angepasst.

Die Startseite lädt jetzt kleinere Vorschaubilder. Große Originale werden erst geladen, wenn man sie tatsächlich öffnet.

Das reduziert Bandbreite und sorgt dafür, dass der Browser weniger Arbeit beim Layout hat. Der eigentliche Performance-Gewinn kam aber weiterhin vom Server.

Was ich aus der Geschichte gelernt habe

Die wichtigste Lektion ist überraschend simpel:

  • Performanceprobleme löst man nicht durch Rumratwn, sondern durch Messungen Und die Spurensuche hat echt Spaß gemacht, war mein erstes Mal.
  • Caching ist hilfreich, aber kein Allheilmittel. Wenn der Flaschenhals im Rendering liegt, muss man verstehen, was genau dort passiert.
  • Oft sind es nicht große, offensichtliche Probleme, sondern viele kleine Kosten pro Eintrag.
  • Baue niemals auf die schnelle irgendetwas ein, ohne dir vorher Gedanken zu machen oder es zumindest zu testen.

Likes & Reaktionen

Kommentar schreiben

Mit dem Absenden stimmst du zu, dass die eingegebenen Daten gespeichert und in Form eines Kommentars dargestellt werden dürfen.