Der Einfluss von Cookies auf die Performance einer Webseite

Cookies werden oft missachtet, wenn es um die Optimierung einer Webseite hinsichtlich Performance geht – zu unrecht! Das wunderbare YSlow von Yahoo zum Beispiel hat einen Test eingebaut, der genau darauf abzielt. In den Performance Grundsätzen dazu heißt die Leitregel Use Cookie-free Domains for Components.

Das Problem

Cookies können erstmal in beliebiger Anzahl und ziemlicher Größe auf Clientseite abgelegt werden. Das Setzen von Cookies geht sowohl mittels Javascript wie auch von der Serverseite aus, bspw. mit PHP. Womit der Cookie gesetzt wird, ist letztendlich egal. Der Effekt auf das Ladeverhalten der Webseite ist in jedem Fall, dass bei jedem HTTP-Request vom Client aus alle Cookies im Anfrage-Header mit übermittelt werden. Heißt konkret: MeineWebseite.com verpasst euch beim ersten Besuch 5 Cookies, jeder 600 Byte groß. Jetzt besteht die Seite aus 30 Images, 5 Javascripts und 8 CSS-Files – garkein unübliches Setup. Das Dokument selbst noch hinzugerechnet haben wir also 1+30+5+8 = 44 HTTP-Requests. (Update: Danke für das Aufdecken des Rechenfehlers)

Damit würde diese imaginäre Seite sogar noch im „Normalbereich“ liegen, verglichen etwa mit mashable.com mit 451 HTTP Requests (!!!). So, zurück auf unser Beispiel. Bei jedem HTTP-Request werden alle Cookies (5 Stück à 600 Byte ~ 3KB) vom Client an den Server übertragen, also bei 44 HTTP-Requests macht das 44*3KB = 132KB zusätzlichen Upload. Das ist schon ’ne Hausnummer. Wenn ich jetzt mal an Leute auf dem Land mit DSL Lite 768 und 128Kbit (16KB/sec) Upload denke, müssten die allein schonmal mehr als 8 Sekunden cookiebedingte Wartezeit in Kauf nehmen – unnütz! Denn wozu brauchen statische Elemente wie Bilder oder CSS-Dateien Zugriff auf die Cookies?

Beispiel-Setup

<?php 	setcookie("PHP-Cookie", "I was set by PHP", time()+3*86400); ?>  <!DOCTYPE html> <html> <head> 	<link rel="stylesheet" href="css/style.css" /> 		 	<script> 	function setCookie(cookiename,value,expiredays) 	{ 		var expires=new Date(); 		expires.setDate(expires.getDate()+expiredays); 		expires = expires.toUTCString(); 		 		document.cookie=cookiename+"="+escape(value)+";expires="+expires; 	} 	 	setCookie("Javascript-Cookie","I was set by Javascript",3); 	</script> 	 </head> <body> 	<img src="images/image.png" /> </body> </html> 

Wir setzen also einen PHP-Cookie und einen Javascript-Cookie (jeweils für 3 Tage, aber das hat für dieses Beispiel keine Bedeutung).

Damit haben wir folgendes Szenario:

Request: http://cookietest.de/index.php Images:  http://cookietest.de/images/image.png Styles:  http://cookietest.de/css/style.css 

Im Firebug betrachtet sieht das so aus:

Der PHP und der Javascript-Cookie wird mit dem Bild (wie mit den anderen beiden Requests auch) mitgeschickt, obwohl weder das Bild, noch die CSS was damit angefangen kann.

Der Cookie-Editor verrät:


Damit sind beide Cookies für die ganze Domain gültig, somit also auch für das Bild und die CSS-Datei.

Lösungsmöglichkeiten

1) Den Content in ein Subdirectory packen und die Cookies auf dieses limitieren

Request: http://cookietest.de/content/index.php Images:  http://cookietest.de/images/image.png Styles:  http://cookietest.de/css/style.css 

Nur die Content-Dateien (PHP-Files) haben in der Regel mit den Cookies zu tun. Wenn wir also die Gültigkeit der Cookies auf dieses Verzeichnis beschränken, bleiben die statischen Ressourcen davon unbehelligt. Damit verändert sich der PHP-setcookie-Befehl wie folgt:

setcookie("PHP-Cookie", "I was set by PHP", time()+3*86400, "/content/"); 

Für Javascript gilt:

function setCookie(cookiename,value,expiredays,path) { 	var expires=new Date(); 	expires.setDate(expires.getDate()+expiredays); 	expires = expires.toUTCString(); 	 	document.cookie=cookiename+"="+escape(value)+";expires="+expires+";path="+path; }  setCookie("Javascript-Cookie","I was set by Javascript",3,"/content/"); 

Damit werden nur noch für die Dateien im /content/* – Unterverzeichnis Cookies übertragen und nicht mehr für Bilder und CSS-Dateien. Nur ist es oft (unter anderem aus SEO-Gründen) nicht sinnvoll, den ganzen Inhalt in einen Unterordner zu verfrachten.

2) Eine Subdomain für alle statischen Inhalte anlegen

Request: http://cookietest.de/index.php Images:  http://static.cookietest.de/images/image.png Styles:  http://static.cookietest.de/css/style.css 

Wenn wir jetzt den Code also so umbauen, dass alle statischen Ressourcen von dieser Subdomain geladen werden, könnten wir es wie folgt aufbauen:

<?php setcookie("PHP-Cookie", "I was set by PHP", time()+3*86400, "/", "cookietest.de"); ?>  <!DOCTYPE html> <html> <head> 	<link rel="stylesheet" href="http://static.cookietest.de/css/style.css" /> 		 	<script> 	function setCookie(cookiename,value,expiredays,path, domain) 	{ 		var expires=new Date(); 		expires.setDate(expires.getDate()+expiredays); 		expires = expires.toUTCString(); 		 		document.cookie=cookiename+"="+escape(value)+";expires="+expires+";path="+path+";domain="+domain; 	}  	setCookie("Javascript-Cookie","I was set by Javascript",3,"/", "cookietest.de"); 	</script> 	 </head> <body> 	<img src="http://static.cookietest.de/images/image.png" /> </body> </html> 

Wenn wir uns jetzt aber den Firebug betrachten, sehen wir allerdings auch wieder bei den statischen Elementen

Cookie	PHP-Cookie=I+was+set+by+PHP; Javascript-Cookie=I%20was%20set%20by%20Javascript 

Wie kommt das?

Der aufmerksame Beobachter sieht sofort: Als Domain ist .cookietest.de eingetragen, was bedeutet, dass die Subdomains ebenfalls davon betroffen sind – Also hilft uns diese Lösung in der Form noch nicht weiter, weil die statischen Dateien trotz Subdomain die Cookies angehangen bekommen.

Die Lösung heißt www.

Wenn wir also nur noch www.-Aufrufe für die Webseite zulassen, können wir die Cookies auch auf www.cookietest.de beschränken. Damit bleibt die Subdomain unbehelligt.

Damit:

Request: http://www.cookietest.de/index.php Images:  http://static.cookietest.de/images/image.png Styles:  http://static.cookietest.de/css/style.css 

PHP:

setcookie("PHP-Cookie", "I was set by PHP", time()+3*86400, "/", "www.cookietest.de"); 

Javascript:

setCookie("Javascript-Cookie","I was set by Javascript",3,"/", "www.cookietest.de"); 

Wenn wir nun noch in der .htacess mittels

#force www. RewriteCond %{HTTP_HOST} ^cookietest.de$ [NC] RewriteRule ^(.*) http://www.cookietest.de/$1 [L,R=301] 

erzwingen, dass die Seite bloß mit www. aufgerufen kann, haben wir gewonnen.

3) Komplett andere Domain für statische Inhalte verwenden

Wir können es uns noch einfacher machen, wenn wir eine komplett andere Domain für statische Inhalte verwenden, etwa cookietest-static.de. Damit brauchen wir uns um nichts mehr zu kümmern, brauchen keine Cookies einschränken oder mit Subdomains herumexperimentieren. Ist halt eine Kostenfrage.

Aber Vorsicht!

Im hier gezeigen Beispiel mit 3 HTTP-Requests wäre der getriebene Aufwand Overkill, da es kaum merkliche Unterschiede in der Seitengeschwindigkeit geben würde. Auch zu beachten ist, dass durch das Auslagern statischer Inhalte auf eine andere Domain ein zusätzlicher DNS-Lookup nötig wird, um cookietest-static.de zu einer IP aufzulösen. Maßgabe ist also, genau abzuwägen wann es sich lohnt, den obigen Aufwand zu treiben.

Zukunftsaussicht

Es wird gerade eifrig diskutiert, ob in Browsern der Zusatz rel=“anonymous“ bei statischen Elementen eingeführt werden sollte. Diese sollen dann keine Cookie-Daten mitgeschickt bekommen. Wäre an sich eine super Sache, nur sind dann trotzdem noch genug Leute mit alten Browsern unterwegs und für per CSS geladene Bilder würde das wohl auch nicht greifen – Mal sehen, wohin die Reise geht.

Bonus: Google Analytics auf eine Domain beschränken

Google Analytics setzt selbst auch einen Cookie mit einer ID, die dann zum Tracking des Benutzers verwendet wird. Mit den Anweisungen _setDomainName und _setCookiePath lässt sich dieser aber auch auf die relevanten Content-Seiten beschränken, um eben zu verhindern, dass auch bei statischen Ressourcen immer der Cookie mit übertragen wird. Bei mir sieht das jetzt so aus:

var _gaq = _gaq || []; _gaq.push(['_setAccount','UA-2935194-4']); _gaq.push(['_setDomainName', 'www.d-mueller.de']);  _gaq.push(['_setCookiePath', '/blog/']); _gaq.push(['_trackPageview']); (function() { 	var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; 	ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; 	var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })(); 


Deprecated: Directive 'allow_url_include' is deprecated in Unknown on line 0