Page Load Time Optimierung für Mobile Netzwerke


David Gunzinger

Die mobile Ladezeit wird von Google als wichtiges Signal für die Bewertung von Seiten verwendet. Wir haben unsere Ladezeit um 50% reduziert und sind damit an die Spitze der Ergebnisliste bei bestimmten Suchanfragen gelangt.

Ausgangslage

Warum haben wir unsere Seite optimiert?

  • PageRank: Google kündigte an, dass neu ab Juli 2018 die mobile Geschwindigkeit einen Einfluss auf den Page Rank haben wird. https://webmasters.googleblog.com/2018/01/using-page-speed-in-mobile-search.html
  • Mobile Anfragen: Wir sind spezialisiert auf Mobile Apps und unsere Website soll möglichst schnell auf mobilen Geräten geladen und dargestellt werden.
  • Spass: Als Entwickler macht es Spass, Software zu optimieren.
     

Was ist speziell an mobilem Internet?

Mobiles Internet unterscheidet sich zum Internet, welches an einem stationären Computer verwendet wird, vor allem durch die hohe Latenz.

Netzwerk

Bandbreite

Round Trip Time

edge

240 Kbps

840 ms

3g

1600 Kbps

300 ms

4g

9000 Kbps

170 ms

lte

12000 Kbps

70 ms

 

 

 

desktop

N Kbps

< 50ms

Wie sah die bisherige Architektur aus?

Die Architektur der Seite besteht aus einem Rails Backend mit Rails Admin, welches als CMS dient und einem Nodejs / React Frontend.

Vorgänging vorgenommene Optimierungen

  • Http2: Die Verwendung von HTTP2 erlaubt, dass alle Anfragen über eine Verbindung durchgeführt werden. Jeder Verbindungsaufbau braucht mindestens 1x Round Trip Time für die TCP Verbindung, 1x Round Trip Time für das SSL Handshake und 1x Round Trip Time für den HTTP Request. Bei HTTP/1.1 müssten wir für ein Laden der Seite mindestens zweimal diesen Verbindungsaufbau machen, bei HTTP2 nur einmal.
  • Server Side Rendering: Die ganze Webseite wird auf dem Server gerendert, für das Navigieren auf der Seite werden jedoch nur die Differenzen nachgeladen. Dies führt zu einem schnellen initialen Aufbau und einem minimalen Aufwand für die Navigation innerhalb der Seite. Die ganze Seite ist auch ohne Javascript benutzbar.
  • Hash URLs: Alle URL's für Ressourcen, wie z.B. Bilder, beinhalten einen Hash des Inhalts und sind mit einem Cache Header ausgestattet, so dass sie nur einmal geladen werden müssen. Cache-Control: public, max-age=31536000, immutable
  • Inline Style Sheets: Das Stylesheet, welches für den Aufbau der Seite benötigt wird, haben wir im React Teil direkt in die Antwort eingebettet.

Messmethoden

  • Pagespeed: Der Klassiker von Google, misst ob alle Cache Header, Komprimierungen, Whitespace Entfernungen und weiteres durchgeführt wurden.
  • Lighthouse: Ein Chrome Plug-In, welches dieselben Parameter wie Pagespeed misst. Zusätzlich wird gemessen, ob die Seite barrierefrei nutzbar ist, ob überzählige CSS Styles vorhanden sind, und vieles mehr.
  • webpagetest.org: Erhebt die gleichen Messdaten wie Lighthouse, allerdings lassen sich zusätzlich Vergleichsvideos erstellen und Leitungsprofile einstellen.

Messung der Ausgangslage:

Die folgende Grafik zeigt den Wasserfall für das Laden der Seite mit 300ms Round Trip Time. Daraus wir ersichtlich, dass enorm viele Bilder geladen werden, welche dem Benutzer nicht initial angezeigt werden.

Vorgenommene Optimierungen

  • Bilder Lazy Laden: Es wurde der IntersectionObserver verwendet, respektive ein Polyfill für Browser, welche IntersectionObserver nicht unterstützen. Damit werden nur die Bilder initial geladen, welche der Benutzer ohne zu scrollen sieht. Für Benutzer die kein Javascript aktiviert haben, wurde mithilfe noscript das Bild normal geladen.
  • WebP: WebP ist ein neues Bildformat von Google, das Bilder bei gleicher Qualität um mehr als 50% komprimiert. Browser, welche WebP unterstützen, senden im Accept Header des HTTP Requests ein Accept: image/webp mit. Bei unterstützen Browsern wird eine WebP Version der Bilder ausgeliefert.
  • font-display: Designer setzten oft Custom Fonts ein. Diese blocken jedoch das Anzeigen des Textes bis sie geladen sind. Das CSS-Attribut font-display erlaubt es, den Text initial mit einem Fallback Font anzuzeigen bis der gewünschte Font vom Server geladen ist.
  • Http2 Server Push: HTTP2 ermöglicht es, initial ohne zusätzlichen Request, Ressourcen vom gleichen Domain mitzusenden. Dies ermöglicht einen Round Trip für die kritischen Ressourcen zu sparen. Um zu verhindern, dass Ressourcen an einen Client gesendet werden, welcher bereits darüber verfügt, wird das sogenannte CASPER verwendet, cache-aware server push. Dabei wird ein Cookie gesetzt, in dem der Wert ein komprimierter Bloom Filter der Hashes von den bereits gesendeten Ressourcen ist. Als HTTP2-Server wurde h2o verwendet, welcher dies bereits von Haus aus unterstützt. 
  • Tcp initial congestion window: Tcp verwendet einen Algorithmus um das Überfüllen der Leitung zu verhindern. Dazu werden zuerst eine Reihe von Paketen geschickt (10 x 1500 Byte) und auf die Bestätigung gewartet. Dann wird die Grösse des Sendefensters verdoppelt. Dies führt jedoch bei Verbindungen mit hoher Bandbreite, über Netzwerke mit hoher Latenz, zu Wartezeiten in denen der Kommunikationskanal nicht gefüllt werden kann. Die Mobile Hersteller (Apple, Microsoft) sind sich dessen bewusst und haben folglich auf ihrer Seite die initiale Fenstergrösse auf 40-50 Pakete erhöht. Wie auch viele CDNs, haben auch wir das gemacht: ip route change default via  [router-ip] dev [device] proto static initcwnd 40 initrwnd 40
  • Caching: Das Rendering und die Kompression einer Response dauert ca. 200ms. Für eine nahezu statische Seite, wie der unseren, möchte man diese Zeit sparen. Die ganze Seite statisch vorzurendern funktioniert leider nicht, da wir eine Suche nach Tags anbieten und wir so mit den kombinatorischen Möglichkeiten der Komprimierungen und der Bildformate tausende von Seiten rendern müssten. Dafür haben wir die Architektur angepasst.
  • Same Domain Fonts: Um die Fonts mittels HTTP2 Server Push zu senden, und keine zusätzlichesn 3x Round Trip Time Verbindungsaufbau zu haben, werden sie neu von unserem Server ausgeliefert.

Optimierungen, die nicht den erhofften Erfolg gebracht haben: 

  • Nur kritisches Css inline: Aufgrund der hohen Round Trip Time und der hohen Bandbreite ist es schneller, das CSS komplett inline auszuliefern.
  • Javascript file split: Aufgrund des gleichen Grundes.

Architektur Neu 

In der neuen Architektur wird die Kompression direkt im Node / React Teil vorgenommen. Varnish speichert von jeder Seite Varianten mit gzip und brotli Kompression. Unkomprimierte Aufrufe werden von Varnish durch Dekompression gehandhabt. Es wird von jeder Seite auch eine Variante mit WebP und eine mit "klassischen" Bildern bereitgestellt. Damit der initiale Aufruf einer Seite nicht zu lange dauert und damit Varnish weiss, wenn er Seiten wieder aus dem Cache entfernen muss, haben wir einen Preloader geschrieben. Der Preloader lädt die Einstiegspunkte in allen Varianten neu und invalidiert den Cache für die Seiten mit der Suche sobald der Content im Rails CMS angepasst wird.

Resultat

Der Wasserfall ist deutlich kleiner geworden, da wir nur noch jene Ressourcen laden, die dem Benutzer direkt angezeigt werden. Alle Antworten starten zur gleichen Zeit, da wir HTTP2-Server-push verwenden. Für das kritische Bild im Header wird WebP verwendet, falls es vom Browser unterstützt wird.


Wie man im Video sieht, hat sich die Ladegeschwindigkeit mit einem LTE Netzwerkprofil mehr als verdoppelt.