Montag, 1. April 2013

Google Hosted Libraries: Oder wieso lokales Hosten falsch ist

Da ich immer wieder sehe, dass Web-Site-Entwickler JavaScript Libraries downloaden und von ihrem eigenen Hosting-Account aus bereitstellen, schreibe ich nun in diesem Artikel, wieso man dies nicht tun sollte, respektive wie man es korrekt angehen soll.

Dabei erkläre ich was hosted libraries sind, und gehe dabei auf das Google-Ajax-API Content Delivery Network ein. Der schnellen Übersichtlichkeit halber aber vorweg:

Falsch:

<script type="text/javascript"
       
src="fileadmin/scripts/jquery/jquery.min.js">
</script>


Richtig:

<script type="text/javascript"
       
src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js">
</script>

Was ist falsch am Hosten von JS Bibliotheken?

Beispiel jQuery: Meistens bleibt es nicht nur bei einer zu ladenden Datei, sondern es werden noch etliche Submodule in eigenen Dateien mitgeladen (z.B. jquery-ui, jquery-colorbox, usw.). Ein Browser lädt diese Dateien im Grunde genommen hintereinander herunter (resp. gemäss HTTP 1.1 Standard RFC 2616 Sektion 8 zwei Downloads parallel, siehe auch mein Blog-Artikel zur Download-Parallelisierung). Während nun diese Downloads getätigt werden, werden keine weiteren Ressourcen der Website heruntergeladen, bis alle Module fertig geladen haben. Das Laden der Website verzögert sich, bis alle für die Bibliothek erforderlichen Files gedownloadet wurden. Doch worauf will ich hinaus?

Ebenfalls im HTTP 1.1 Standard ist beschrieben, dass die Limitierung von parallelen Downloads pro Host gilt. Wenn wir also die Javascripts von woanders her laden, dann geschieht dies in einer "eigenen Welt", und unsere Website kann munter die Ressourcen herunterladen, die es für die effektive Anzeige auch braucht. Der Download unserer Bilder, CSS usw. wird nicht blockiert durch Library-Downloads. Dazu kommt noch, dass wir die Bandbreite etwas schonen.

Was sind die Vorteile?

  1. Die Libraries werden von Google auch mit Cache-Header versehen, welche 1 Jahr gültig sind. Da nun weltweit viele Websites das CDN verwenden, ist die Chance gross einen Besucher zu haben, der die Library bereits in seinem Cache hat. Damit muss der User die Library nicht nochmals downloaden, sondern behält sie in seinem Browser-Cache für alle Websites, welche die Library verwenden.
  2. Kein Hosting, keine Bandbreite, kein Storage benötigt für die Libraries
  3. Einfaches Umschalten auf eine neuere Library-Version: Anstatt die neue Version zu downloaden und zu installieren, kann einfach das script-src Attribut dahingehend geändert werden, dass eine andere Version angefordert werden soll.
  4. Profitieren von der massiven Performance des Google-CDN

Kontras?

Wenn ich Leute frage, wieso sie die Libraries nicht über Google API CDN beziehen, sondern selber bereitstellen, erhalte ich in der Regel die folgende Antwort: Mein Web-Hoster ist schneller als das Google CDN, ich habs gemessen!

Das mag sein, solange der Web-Hoster mehr oder weniger dein Nachbar ist. Was aber, wenn ein User aus New-York deine Seite abruft? Ein 3-Way TCP Handshake zwischen Europa und USA hat einfach seine rund 60-80ms Latenz, schon nur das Licht braucht seine 30ms Reisezeit. Das Google CDN ist aber dezentral gehostet - sprich der Amerikaner kriegt seine JavaScript Libraries von einem dort lokalen CDN, und in Europa kriegen wir sie von einem für uns lokalen CDN. Global gesehen, ist es also vernünftiger, die CDN zu verwenden. Man kauft seine Kopfsalate auch nicht Übersee, wenn man sie im Supermarkt gegenüber kriegen könnte.

Was ich auch schon hörte ist, dass Leute Probleme damit haben, die Kontrolle über ihre JavaScript Libraries "weg zu geben". Ich denke, dieses Argument ist objektiv und sachlich relativ schnell zu entkräften. Ein Content Delivery Network ist weltweit dezentral angelegt, und die Leute von Google wissen wohl, wie man hoch verfügbare Services bereitstellt. Eine Google CDN Downtime ist mir zumal noch nie zu Ohren gekommen, während ich von verschiedenen Hostern schon Downtimes wegen "Wartungsarbeiten" in Kauf nehmen musste.

Welches CDN soll ich wählen (Daten aus 2012)?

Um beim Beispiel von jQuery zu bleiben, gibt es drei grosse CDN, welche diese Libraries anbieten: Google, Microsoft und MediaTemple. Es stellt sich die Frage, welches CDN gewählt werden soll. Das Unternehmen Pingdom (ein Monitoring Service Anbieter) hat über 30 Tage hinweg jeweils einmal pro Minute eine Library von den drei CDN bezogen, und nebenstehende Grafik erstellt.

Daraus ist ersichtlich, dass Media-Temple die besten Antwortzeiten hat. Wenn der Content aber über HTTPS geliefert werden sollte, dann ist das Google CDN die erste Wahl.

Weitere Libraries?

Google hostet nebst jQuery noch folgende Libraries. Ist eine Version einmal veröffentlicht, wird diese unendlich lang gehostet:

Donnerstag, 21. März 2013

Nutzen der Browser Download-Parallelisierung für schnellere Websites

In einigen früheren Posts habe ich angesprochen, dass ein Browser nur z.B. 6 Ressourcen gleichzeitig downloaden kann, und die nächsten 6 dann in einem zweiten, dritten, vierten Schritt herunterlädt. Moderne Websites brauchen oft gegen 50-60 Ressourcen (Bilder, Javascript, CSS), was dann die Website im ersten Aufruf langsam erscheinen lässt, da es 10 hintereinander liegende Download Durchgänge braucht.

Dies resultiert darin, dass eine Website im blödsten Falle blockiert bis alle Ressourcen gedownloadet wurden, oder dass die Seite progressiv aufgebaut wird - was je nach Anwendung störend sein kann.

In diesem Artikel beschreibe ich, wie man mit dieser Limitation um gehen kann.

Wieso limitiert ein Browser die Download-Warteschlangenlänge?

Der HTTP 1.1 Standard in der RFC 2616 Kapitel 8.1.4 beschreibt, dass ein Browser maximal 2 Ressourcen gleichzeitig vom gleichen Host anfordern sollte. Mittlerweile downloaden die meisten modernen Browser entgegen dem Standard 6-8 Ressourcen gleichzeitig. Doch wieso überhaupt diese Limite?

Die RFC wurde zu Zeiten geschrieben, als das Web noch langsam, die Bandbreiten limitiert und die Server für heutige Verhältnisse absolut unperformant waren. Die Limite dient zum Schutz von Webservern vor Überlastung. Die Limiten wurden heute in den Browsern erhöht, doch sind sie noch da. Ja, das Web ist schneller, die Server performanter... aber die Websites viel umfangreicher!

Parallelisierung von Downloads über verschiedene Hosts

Hat man nun eine Website, die aus diversen Ressourcen (z.B. vielen Bildern) besteht, so ist der einfachste Trick der, dass man die Ressourcen über verschiedene Domain Namen verteilt. Dabei müssen die Domains nicht mal auf verschiedenen Hosts liegen - es reicht bereits aus, wenn es sich um Domain Aliase handelt, die sich im gleichen Hosting auf dem gleichen Server befinden.

Beispiel Google Maps

Im nachfolgenden Beispiel werden von Google Maps diverse Kacheln für die "Satelliten-Ansicht" geladen. Dies sind weitaus mehr als 6 Stück, und sie werden über diverse Domain-Aliase verteilt; laden damit also alle gleichzeitig:


Das gleichzeitige Laden ist nötig, damit die Karte sofort erscheint, und nicht zeilenweise aufgebaut werden muss. Im Screenshot sind in der ersten spalte die verschiedenen Domain Aliase ersichtlich, und ganz rechts die Startzeitpunkte der Downloads.

Falltüren

  • Kann der Host so viele gleichzeitige Requests auch handhaben?
  • Wie viel Zeit braucht ein DNS Lookup, um den Domain-Namen in eine IP-Adresse umzuwandeln?
Die erste Frage zu beantworten bedürfte einen neuen Post (Ideen für neue Posts sind immer willkommen!). Beim Apache ist die Frage zum Beispiel mit der Anzahl Sub-Prozessen des Servers beantwortbar. Hier muss man einfach etwas abwägen und ausprobieren.

Die zweite Frage zumindest kann mit Messungen beantwortet werden. Ein DNS Lookup braucht einige Milisekunden Zeit, und wird durch eine Round-Trip-Time verzögert. Allerdings muss ein Lookup nur einmal gemacht werden, danach wird das Resultat vom Browser, vom Betriebsystem und schlussendlich vom lokalen DNS Server zwischengespeichert. Hier kann man nur Erfahrungswerte nennen: Aus eigener Erfahrung holt man das Optimum bei vielen Ressourcen über 3 bis maximal 5 Domain Aliase heraus.

Wann lohnt sich die Technik der DNS Aliase?

Bei Websites mit weniger als 10-20 Ressourcen Anforderungen würde ich empfehlen, gar nichts zu unternehmen. Danach sollte man anfangen, in Sechser-Schritte die Ressourcen auszulagern, also etwa so:
  • Alle CSS-Files (z.B. 5 Stück) auf static01.insor.ch
  • JavaScript-Files (z.B. 15 Stück)
    • 6 auf static01.insor.ch
    • 6 auf static02.insor.ch
    • 3 auf static01.insor.ch
  • Alle Bilder (z.B. 30 Stück)
    • 6 auf static02.insor.ch
    • 6 auf static03.insor.ch
    • usw.
Wichtig ist dabei auch, die Ladereihenfolge der CSS- und Javascript Files zu beachten, und die Javascripts allenfalls mit dem Script Attribut "async" zu laden. Danach muss man sich überlegen, in welcher Reihenfolge die Bilder geholt werden, und welcher Domainname zum entsprechenden Zeitpunkt wohl am "wenigsten ausgelastet" sein dürfte - und so sind die Files zu verteilen.

Zu beachten ist, dass die Files aber immer von der selben Domain aus angeboten werden - so können wir die Browser-Caching-Mechanismen benutzen. Würden die CSS-Dateien plötzlich von einer anderen Domain referenziert werden, müsste der Browser alles erneut downloaden.

Schlussendlich kommt man nicht darum herum, die Sache mit FireBug oder ähnlichem Tool einfach mal zu testen und etwas rum zu spielen, bis man das Optimum heraus geholt hat.

Und sonst...?

Bei grossen Ressourcen spielt dann bald auch mal die Bandbreite eine Rolle. Hier könnte man sich überlegen, die Ressourcen auf ein Content Delivery Network auszulagern. Die meisten CDN unterstützen zudem das Anbieten der Ressourcen über verschiedene Domain Aliase.

Feedback

Habt ihr Erfahrungen mit dieser Optimierungstechnik gemacht? Bitte kommentiert doch mit der entsprechenden Kommentarfunktion unten. Ich bin auch immer offen für Anregungen für neue Themen!

Donnerstag, 14. März 2013

Minimieren von HTTP Requests: Single CSS/JS und Sprites

In meinem letzten Artikel über das Verteilen von Ressourcen auf Content Delivery Networks habe ich das Thema schon angeschnitten, dass man die Anzahl HTTP Requests minimieren sollte, um den Multiplikator der Round-Trip-Time gering zu halten. Nun erkläre ich, wie man das Maximum herausholt, indem man Ressourcen zusammenfasst.

Was ist das Problem bei Anforderung zahlreicher Ressourcen?

Die allermeisten Websites laden mehrere CSS und mehrere Javascript Dateien - gerade bei letzteren für einzelne Module der Übersichtlichkeit halber über diverseste Files. Der HTTP 1.1 Standard empfiehlt, dass ein Browser maximal 2 Ressourcen parallel von einem Host anfordert - die meisten Browser fordern zwar mehr an (ca. 6), aber das Problem ist dasselbe: Sind Javascripts über 24 Files verstreut, kann ein Browser diese maximal in einer Sechser-Parallelisierung downloaden - in unserem Fall wären das 4 mal hintereinander 6 parallele Downloads. Folgendes Netzwerk-Audit einer Website mit 20 Javascript Referenzierungen im Source (insgesamt 197 angeforderte Ressourcen)  illustriert dies, man beachte im Tooltip den Wert unter "Blocking" (damit ist gemeint, dass der Browser so lange blockiert war, bis er den Download resp. die Verarbeitung einleiten konnte):


Zum Zeitpunkt der Anforderung dieser Ressource, verbrachte der Browser also rund eine halbe Sekunde nur mit blockierendem Warten.

In meinem Artikel über den Einsatz von Content Delivery Netzwerken (CDN) habe ich berichtet, dass wir dies entschärfen könnten, indem wir diese Ressourcen auf einen anderen Host legen - denn die Limite von 6 parallelen Download bei modernen Browsern gilt per Host.

In einem anderen Artikel von mir habe ich zudem den Einsatz von Cache-Control-Headers empfohlen, womit die Ressourcen nach dem ersten Download im Browser-Cache verbleiben und so nicht jedes Mal neu angefordert werden müssen.

Tipp 1: Zusammenfassen von CSS

Dies ist wohl die einfachste Möglichkeit: CSS wird sequentiell abgearbeitet, also würde es reichen, die CSS Dateien in der richtigen Reihenfolge einfach in eine einzige Datei zusammenzufügen. Damit haben wir nur noch eine CSS Datei, und brauchen damit auch nur einen Request zu machen. 

Es kommt natürlich oft vor, dass gewisse CSS Dateien nur für einzelne Seiten gelten - diese kann man aber auch einzeln belassen. Das Ziel ist einfach, nicht unnötig viele Dateien zu haben. Also weg vom "modularisierten" Denken, wenn es um Performance geht!

Wem dies zu unübersichtlich wird, der kann auch ein kleines Script schreiben, welches ihm die CSS Dateien zusammenfasst und als gecachte Datei auf dem Webserver ablegt. Dies liesse sich soweit automatisieren, dass wenn das Datum der Source Dateien ändert, das Script bei einer neuen Anforderung ein neues Master-CSS generiert und ausliefert. Der Scripting Overhead und die mittlere Filesystemzugriffszeit von einigen Milisekunden machen sich bezahlt, verglichen mit der enormen Zeit für mehrere Round-Trips (vor allem wenn es mehr als 6 sind).

Tipp 2: Zusammenfassen von Javascript

Auch JavaScripts werden sequentiell abgearbeitet, also lassen sich auch diese in einer einzelnen Datei zusammenfassen - nach dem gleichen Prinzip wie bei den CSS Dateien. Ein Paradebeispiel ist jQuery, welches aus einem Master-JavaScript, und für jedes Modul aus einem weiteren CSS-Script besteht. Eine aufwändig interaktive Website wird schnell mal 10-20 solche Module laden, und das sind einfach wieder 4 Round-Trips à 6 parallelen Downloads.

Tipp 3: Arbeiten mit CSS-Sprites

Dabei packen wir das letzte Problem beim Kragen, und das sind übermässig viele Bilder, die einzeln mit HTTP-Requests vom Webserver angefordert werden. Auch hier gilt die Limite von 2 parallelen Downloads gemäss HTTP 1.1 Spezifikation. Was wir tun können ist folgendes: Anstatt 20 kleine Bilder zu erstellen (z.B. Runde Ecken, Buttons, usw.) - erstellen wir ein einzelnes, sehr breites Bild, in welchem alle diese Elemente enthalten sind. Danach positionieren wir die Bilder mit den CSS-Direktiven "background-image" und "background-position", so dass in einem DIV nur jener Ausschnitt des Bildes angezeigt wird, der für das DIV (z.B. eine Schaltfläche) auch interessant ist.

Damit reduzieren wir die Anzahl Requests für 20 Bilder auf einen. Dieser Tipp funktioniert natürlich nur mit Bildern, die wir effektiv per CSS verwenden. Werden die meisten Bilder mit <img> Tags geladen, würde die Umsetzung dieser Strategie zu einem Reeingineering des Web Templates führen. Ob dies den Aufwand rechtfertigt, ist im Einzelfalle abzuklären.

Ein gutes Beispiel für die CSS Sprite Technik sind zum Beispiel Landesflaggen. Anstatt 265 Requests vom Webserver anzufordern, holt man sich das Sprite mit einem einzigen Request. Die Ladeperformance ist definitiv spürbar!

Tipp 4: Minifizieren (verkleinern) von CSS und JS

Es existieren diverse Tools (wie z.B. der YUI Compressor), die aus CSS und Javascripts Leerzeilen, Abstände und Kommentare rausnehmen, um die Grösse der Dateien zu reduzieren. Bei Javascript werden selbst Funktions- und Variablennamen auf ein Minimum abgekürzt. Natürlich ist der Code danach nicht mehr leserlich. Der Hintergedanke ist aber auch, dass man dies nur für die endgültige Version macht, die dann auch bereitgestellt wird. Dies liesse sich mit unter Tipp-1 genanntem Script ebenfalls automatisieren, was ich aber nicht empfehle. Eine minimierte Version eines CSS/JS will trotzdem noch getestet sein.

Bei eingeschalteter serverseitigen Komprimierung ist die Minifizierung fast ohne Gewinn; man holt durch die fehlenden Kommentare usw. in der Regel kaum viel mehr als weitere 5% heraus.

Fazit

Würde man es schaffen, dass eine Website nur aus einem HTML-, einem CSS- und einem Javascript Dokument besteht; und würde man die Bilder mittels der CSS-Sprites Techniken in nur ein Bild packen, so würde die Website mit einem modernen Browser sämtliche Ressourcen im ersten und einzigen Durchgang downloaden können. Während die CSS-Sprite-Technik aufwändig umzusetzen ist, so ist das Zusammenfassen von JS und CSS aber sicher ein kleiner Aufwand. 

Eine umfassende Websites mit 30 CSS und JS Dateien (gerade bei JS Libraries), und einer RTT von 50ms, spart sich damit 200ms an blockierender Wartezeit durch Parallelitätslimitierungen. Kommt ein Besucher aus Übersee, fällt die RTT hier massiver ins Gewicht. Extrem wird es bei Nutzern von mobilen Endgeräten, wo die RTT in der Regel über 200ms liegt. Auf der Strecke Zürich-Bern mit den schweizerischen Bundesbahnen habe ich nicht selten eine RTT von 2000ms über 3G-Netzwerke. Was das für das Laden von 265 Flaggen bedeutet, liegt auf der Hand.

Samstag, 9. März 2013

Speed und Ausfallssicherheit durch Content Delivery Networks (CDN)

In meinem letzten Artikel über die Ladereihenfolge von CSS und JS Dateien habe ich berichtet, dass man damit die Parallelität der Downloads positiv beeinflussen kann, wenn man dabei einige Grundregeln beachtet. Nun gehe ich einen Schritt weiter und zeige, wie man diese Ressourcen auf CDN's verteilt und damit noch höhere Performance rausholen kann.

Dieser Artikel ist vor allem interessant für Betreiber trafficstarker Websites.

Was sind Content Delivery Networks (CDN)?

Dabei handelt es sich um grosse Provider wie Akamai, Amazon, CloudLayer, usw. Diese fungieren als Reverse-Proxies für bestehende Websites, können aber auch verwendet werden, um häufig gebrauchte Ressourcen auszulagern und somit den "kleinen, lokalen" Hostinganbieter zu entlasten. Wieso würde man dies wollen?
  1. Shared-Hosting: Dabei teilt man sich den Webspace mit anderen. Entsteht eine Last auf einer Website eines anderen Kunden, wird die eigene Website ebenfalls runtergezogen, das Laden von einzelnen Ressourcen zieht sich hin, und dies beeinflusst die Gesamtladezeit der Website negativ.
  2. Traffic-Limiten: Bei den meisten Hostinganbietern ist nach einem Terabyte Schluss, oder es fallen vergleichsweise hohe Gebühren an
  3. Bandbreiten-Limitierung: Die meisten Hosting-Anbieter drosseln die Bandbreite pro (dedizierten) Server auf 100mbit/s (Stand Januar 2013). Finden durch verschiedene User nun Streamings von Videos, oder Downloads statt, bleibt keine Bandbreite mehr für das Laden der Page
  4. Traffic-Burst: Gerade bei Werbekampagnen kann es gut sein, dass ein Peak entsteht, welcher der Hostingserver nicht zu verarbeiten vermag. Das Resultat sind enorm degradierte Ladezeiten oder Totalausfälle.
Auf den vierten Punkt gehe ich in diesem Artikel nicht ein, dies ist ein anderes Thema. Dies wird in der Regel ebenfalls mit dem Einsatz von CDN gelöst.

Wie sind CDN aufgebaut?

Vereinfacht gesagt, sind dies Storage- und Netzwerkprovider. Ihr ladet eine Ressource hoch, und kriegt hierfür eine Zugriffs-URL. Der CDN Provider unterhält weltweit diverse synchronisierte Datacenters, und die angeforderten Ressourcen werden immer von demjenigen Rechenzentrum geladen, welches sich am Nächsten zum aufrufenden User befindet. Ein User in China wird also die Ressource aus einem Rechenzentrum in China abrufen; ein User aus Deutschland dann z.B. aus einem Rechenzentrum in Irland.

Diese CDN Anbieter verrechnen dabei den verwendeten Storage, den verwendeten Traffic und die bedienten HTTP Requests (als Beispiel, die Verrechnungsmodelle sind vielfältig).

Umsetzung

Diverse Ressourcen einer Website sind statisch (vor allem grosse Banner-Bilder, aber auch CSS, JavaScript, usw.). Anstatt sich hier nun auf eher langsame Leitungen oder Server zu verlassen, könnte man diese Ressourcen auf ein CDN auslagern. Das Vorgehen ist dabei wie folgt:
  1. Anmeldung bei einem CDN
  2. Upload der Ressourcen, respektive Sync der gesamten Website mit dem CDN
  3. Lösen eines Domainnames, damit auf die Ressourcen des CDN zugegriffen werden kann (in der Regel  wird einem eine solche URL mitgeteilt)
  4. Ersetzen der lokalen Referenzierung von Ressourcen (js, css, img) in den HTML Sourcen mit externen Referenzierungen auf das CDN

Was erreicht man damit?

Zu beobachten sind zwei Haupteffekte:
  1. Erhöhung der Parallelisierung der Ressourcen-Downloads: Ein Browser lädt in der Regel 6 Ressourcen gleichzeitig vom selben Host herunter, dann die nächsten 6, usw. Durch die Verwendung von CDN kommen diese Ressourcen von einem anderen Ort, werden also parallel zu allen anderen lokalen Ressourcen heruntergeladen. Der Geschwindigkeitszuwachs dürfte vor allem bei Bannerbilder usw. spürbar sein.
  2. Tiefere Round-Trip-Times bei ausländischen Besuchern: Ein Besucher aus den Staaten hat eine relativ hohe Latenzzeit zwischen Anfrage und Antwort vom Server - dies ist rein physisch durch die Distanz bedingt. Dadurch, dass die Ressourcen von einem für den User lokalen CDN-Mirror geholt werden, verringert sich die RTT auf ein Minimum, was absolut spürbar ist. Beispiel: Zwischen London und New York beträgt die RTT auf neuesten Leitungen ca. 60ms. Das ist reine Wartezeit bedingt durch die Distanz. Werden diverse Ressourcen sequentiell geladen (z.B. Javascripts), summiert sich diese Zahl entsprechend schnell auf.
  3. Erhöhte Bandbreite, womit die Ressourcen schneller heruntergeladen werden
Weitere positive Nebeneffekte:
  1. Die CDN übernehmen das Cache-Controlling. Wer dies also nicht selber machen kann (siehe mein Artikel über Cache-Control-Headers), der muss sich beim Einsatz von CDN nicht mehr darum kümmern.

Einrichtung und Kosten?

Die Sache ist einfacher als man denkt. Angemeldet hat man sich bei einem CDN schnell, und das syncen einer Website hat man auch innert meist nützlicher Frist hinter sich.

Die Kosten fallen wie folgt an (Beispiel Amazon CloudFront, Preise Europa und USA, Stand März 2013):
  • Ca. 10 Rappen pro Gigabyte Traffic
  • Ca. 1 Rappen pro 10'000 HTTP-Requests
Eine durchschnittliche Website wird ca. 10-20 Requests pro Seitenaufruf auslösen (pro Ressource ein Request, ausser, die Ressource ist lokal gecached im Browser, z.B. mittels mod_expires). Wenn ein Besucher im Durchschnitt 5 Seiten aufruft, so sind dies 100 Requests pro Besucher. Eine Page mit 2000 Besucher pro Tag wird damit auf 200'000 Requests im Tag kommen (=Hits), womit dies 20 Rappen pro Tag ausmacht. Je trafficreicher die Seite, desto höher werden die Kosten.

Eine News-Seite wie z.B. 20 Minuten Online mit täglich 468'000 Besucher (Mediadaten vom Februar 2013) à 20 Pageloads à 40 Assets verursacht andere Kosten: Dies sind täglich 374 Mio Hits, was pro Tag 370 Franken ausmachen würde. Dies wäre aber immer noch weitaus günstiger, als eine eigene High-Performance Infrastruktur zu betreiben.

Nicht beachtet sind hier die Traffic-Kosten per Gigabyte. Diese dürften durch die Caching-Mechanismen für eine normale Website aber nicht ins Gewicht fallen (meine Faustregel: pro 1000 monatliche Benutzer 1 GB pro Monat). Bei einem grossen News-Netzwerk mit viel Bild- und Videomaterial sieht das allerdings etwas anders aus. Allerdings kann man dann bei den CDN den Traffic vorab einkaufen, womit die Preise nochmals massiv tiefer sind.

Optimierung der Kosten

Die Anzahl HTTP-Requests kann man natürlich optimieren, indem man CSS und JavaScript Dateien je zusammenfasst, und mit CSS-Sprites arbeitet anstatt mit Einzelbildern. Dies hat dann ohnehin wiederum einen positiven Effekt auf die Seitenperformance.

Fazit

Interessant, wenn die Ladegeschwindigkeit ein wichtiges Geschäftskriterium ist. Tiefe Einstiegskosten erlauben es selbst kleinen Unternehmen, von CDN Gebrauch zu machen. Die Hauptvorteile kommen allerdings erst dann zum tragen, wenn entsprechende Übertragunsgvolumen technisch oder preislich durch klassisches Hosting zum Problem werden.

Sonntag, 3. März 2013

DOM Load mit sinnvoller Ladereihenfolge von JS und CSS beschleunigen

Für den Einsatz von Javascript-Widgets wie Slideshows, Lighboxes, und vielleicht noch den einen oder anderen Ajax-Request muss man beachten, dass man die Libraries und CSS-Files dazu in korrekter Reihenfolge lädt. Durch geschickte Deklarationsreihenfolge der Libraries lässt sich die Zeit verringern, bis die Website angezeigt wird.

Tipp: Deklarationsreihenfolge von CSS und JS beachten

Immer erst die CSS-, und erst danach die JS-Deklarationen - sofern dies die Applikationslogik zulässt. Wieso? Der Grund liegt in der Art der Parallelisierung der Downloads dieser Ressourcen.

Die meisten Browser blockieren den Download von im Dokument angegebenen Ressourcen nach einem Script-Tag, bis der Inhalt des Script-Tags gedownloadet, geparst und fertig ausgeführt ist. Dies ist soweit logisch, denn die Javascripts müssen sequentiell abgearbeitet werden, da sie unter Umständen das DOM verändern und von Aktionen vorangehender Scripts abhängig sein könnten. Anders sieht das bei CSS aus: Dies sind lediglich Styling-Informationen, und können allesamt parallel heruntergeladen werden (d.h. in der Regel 6 Files pro Domain aufs Mal, je nach Browser). Natürlich kann es sein, dass eine CSS-Rule eine andere wieder überschreibt, aber das führt nicht zu Inkonsistenzen im DOM.

Würde man nun also 2 externe Javascript-Dateien referenzieren, und danach 2 CSS-Dateien, so würden die CSS-Dateien erst heruntergeladen, nachdem die 2 Javascripts sequentiell durchgelaufen sind. Vorher blockiert der Browser jeden weiteren Ressourcen-Download. Dies sieht etwa so aus:


<script ... />
<script ... />
<link rel="stylesheet" ... />
<link rel="stylesheet" ... />

   50ms | 100ms | 150ms | 200ms |
--> JS1 --> JS2  
               --> CSS1
               --> CSS2


Die beiden JS werden geladen, und dann folgt der Rest parallel. Wie würde es aber aussehen, wenn wir zuerst die CSS, und dann die JS laden würden?


<link rel="stylesheet" ... />
<link rel="stylesheet" ... />
<script ... />
<script ... />  

   50ms | 100ms | 150ms | 200ms |
--> CSS1
--> CSS2
--> JS1 --> JS2

Angenommen, alle Files haben 50ms zum Laden, so hatten wir zuerst 150ms Ladezeit, nun sind es noch 100ms. Damit haben wir 30% Performance raus geholt.

Fazit

Weit hergeholt? Nein. Dieses Beispiel illustriert lediglich 2 Javascripts und 2 CSS-Dateien, alle von gleicher Grösse. In der realen Welt sieht es ganz anders aus. Fremdlibraries werden wie wild referenziert und  oft auch verkettet geladen, und eben in einer für den Browser ungünstigen Reihenfolge. Mit einer sinnvollen Parallelisierung holt man selbst bei guten Roundtrips schnell mal 50-100ms raus.

Natürlich könnte man nun sagen, man habe meinen Artikel über die Optimierung mittels Cache-Control-Header gelesen, und die JS-Dateien würden ja nur einmal heruntergeladen, und dann aus dem Browser-Cache bedient werden. Dies mag für subsequente Page-Aufrufe stimmen. Aber: Die JS-Dateien müssen dennoch jedes Mal geparst und ausgeführt werden, und das kostet Zeit. Auf einem Core-i7 mit Google Chrome sind dies bei der weit verbreiteten jQuery Library etwa 20ms, mit dem IE7 sind es immerhin ca. 100ms. Und dies bei jedem Seitenaufruf...

Fussnote

Natürlich gibt es diverse Ausnahmen, welche ich in diesem Artikel nicht erwähnt habe. Zum Beispiel lässt sich dem <script> Tag das "async" Attribut anhängen, womit die Scripts nicht aufeinander warten müssen. Des weiteren kennen moderne Browser intelligente Prediction-Modelle, über welche sie vorzubestimmen in der Lage sind, ob weitere Ressourcen schon mal gedownloadet, aber noch nicht ausgeführt werden sollen. Darauf bin ich natürlich nicht eingegangen, auch wenn moderne Browser diese Modelle in gewissen Situationen durchaus umsetzen. Hält man sich aber an obige Regel (Css-vor-Javascript), muss man sich darüber ja gar nicht erst den Kopf zerbrechen und ist immer auf der sicheren Seite.

Samstag, 23. Februar 2013

Optimierung von Websites mittels .htaccess (mod_deflate & mod_expires)

Ohne die eingesetzten Content-Delivery-Systeme zu kennen: Die folgenden beiden Massnahmen eigenen sich für alle Websites, und brauchen ebenso meistens keinen Eingriff in die Serversystem Software. Die Beispiele richten sich an den Apache httpd Webserver, die Prinzipien gelten aber auch für IIS, usw. Auf den meisten Shared-Hostings sind diese beiden Massnahmen standardmässig nicht umgesetzt.

Tipp 1: Serverseitige Komprimierung (mod_deflate)

Dieser Tipp gilt vor allem dann, wenn der Server über ausreichend CPU-Leistung verfügt, und die Clients eher mit geringen Datenübertragungsraten die Site besuchen (Mobiltelefone, usw.). Nach Aktivierung der Kompression, ist die Gesamtgrösse der übertragenen Daten um einiges geringer:

Serverseitige Kompression verringert die übertragene Datenmenge markant. Eine minifizierte jQuery Library wird hier von 91 KB nochmals auf 33 KB reduziert.
Auf Apache bspw. kann mit dem mod_deflate Modul die On-the-Fly Kompression von diversen Files konfiguriert werden. Das Modul ist auf den meisten Shared-Hosts von Providern zwar vorhanden, aber nicht aktiviert. Um dies zu tun, ist folgender Eintrag in die .htaccess Datei notwendig:

<ifmodule mod_deflate.c>
AddOutputFilterByType DEFLATE text/text text/html text/plain text/xml text/css application/x-javascript application/javascript text/javascript
</ifmodule>

Mit der Direktive "ifmodule..." wird sichergestellt, dass die Anweisung nur dann ausgeführt wird, wenn das Modul auch installiert ist - ansonsten riskiert man einen 500er Fehlercode.

Die Einstellung greift sofort, es ist also kein Neustart des Webservices notwendig.

Tipp 2: Cache Control (mod_expires)

Dieser Tipp sollte auf jeden Fall immer angewendet werden, und ist bei den meisten Shared-Hosts von Providern zwar auch wieder vorhanden, aber nicht aktiviert. Hierbei geht es darum, dass in unserem Beispiel der Apache Webserver mit jedem gesendeten Asset eine zusätzliche Header-Zeile mitliefert. In dieser steht drin, dass der Browser das Asset bei sich lokal ablegen, und erst zu angegebenem Zeitpunkt das Asset wieder beziehen soll. Eine JPEG-Grafik, die 1MB gross ist, wird also auf der lokalen Festplatte des Users abgelegt, und danach immer von dort geholt anstatt vom Server. Folgendes Bilder illustrieren die Ladezeiten bei aus- und bei eingeschalteten Expires-Header:

Nicht eingeschaltete Expires-Header: Jede Datei wird beim Aufruf der Seite erneut  abgefragt. Der Webserver ist hier intelligent genug, einen 304-Not-Modified Header zu senden, anstatt die Datei nochmals zu schicken. Dennoch braucht der Roundtrip hierzu einige Zeit!
Eingeschaltete Expires-Header: Der Webserver wird nun gar nicht mehr angefragt, sondern die Dateien werden direkt aus dem Cache des Browsers geladen. Resultat: Keine Roundtrips und damit keine Verzögerungen!
Für die Aktivierung ist folgender Eintrag in die .htaccess Datei notwendig:

<FilesMatch "\.(js|css|png|jpg)$">
  <IfModule mod_expires.c>
    ExpiresActive on
    ExpiresDefault "access plus 7 days"
  </IfModule>
  FileETag MTime Size
</FilesMatch>

Damit werden alle *.js, *.css, *.png und *.jpg Dateien beim Senden an den Client, mit dem Header versehen. Dieser besagt, dass die Dateien 7 Tage gültig sind (und sich in dieser Zeit auch nicht ändern). Würde ein User aber den "Reload"-Button im Browser betätigen, würden alle Assets neu gedownloadet - unabhängig vom Expires-Header. Man ist also bei einer fertig entwickelten Webpage damit auf der sicheren Seite.

Fazit

Die beiden Optimierungen erfordern keinen grossen Aufwand, und setzen keinen Eingriff in die Content-Management/Delivery Software voraus. In den meisten Fällen lassen sich mit diesen Anpassungen mindestens gegen 200-300ms rausholen. Bei einer Page-Load-Time von 1 Sekunde, ist dies ein Zuwachs der Geschwindigkeit von 30% - die für den User auf jeden Fall spürbar ist.