Nginx mit PHP5-FPM ist schon recht schnell, werden fertige Seiten mittels Memcached gespeichert lässt sich die Ladezeit noch weiter reduzieren. Die Umsetzung ist einfach.
Dieser Beitrag ist inspiriert von einer Anleitung auf 6tech.org und auf aktuelle Software-Versionen übertragen. Folgende Voraussetzungen müssen erfüllt sein, damit das Nginx-Setup von Memcached profitieren kann:
- nginx mit PHP5-FPM
- php5-memcached-Module installiert und aktiviert
- memcached muss installiert sein
Die Grundidee: statt der eigentlichen index.php wird ein Zwischenscript aufgerufen, welches den Aufruf an die eigentliche index.php weitergibt und das Ergebnis im Memcache ablegt. Dadurch erhält man auf einfachem Wege eine eine Caching-Lösung ähnlich des Plugins WP Supercache, bei der die die fertig erstellten Seiten hinterlegt werden. Dadurch ist im Falle eines Cache-Hits nur ein PHP-Aufruf nötig um das Memcache-Ergebnis auszuliefern – da die Daten im RAM abgelegt werden ist dies deutlich schneller als ein regulärer Aufruf.
index.php-Ersatz
Folgendes PHP-Script muss im Verzeichnis des zu cachenden WordPress abgelegt werden, zum Beispiel als index-memcached.php.
<?php $start = microtime(true); $memcached = new Memcached; $memcached->addServer('127.0.0.1', 11211); // Cache time in seconds (1 day) $cacheTime = 60 * 60 * 24 * 1; $cacheKey = "fullpage:{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}"; $debugMessage = 'Page retrieved from cache in %f seconds'; $html = $memcached->get($cacheKey); if ( ! $html) { $debugMessage = 'Page generated in %f seconds'; ob_start(); require 'index.php'; $html = ob_get_contents(); $memcached->set($cacheKey, $html, $cacheTime); ob_end_clean(); } $finish = microtime(true); echo $html; echo '<!-- ' . sprintf($debugMessage, $finish - $start) . ' -->'; exit;
Die Caching-Dauer muss in Sekunden angegeben werden, die Formel ergibt ein Caching von einem Tag. Ist eine Seite also erst einmal im Memcache gelandet wird sie erst nach einem Tag aktualisiert. Dies ist zu beachten wenn es zum Beispiel um Kommentare geht: verwendet man die WordPress-Kommentarfunktion würden die Beiträge von Lesern nicht aktualisiert werden. Durch das hier verwendete Disqus-Comment-System mit Lazyload wird dieses Problem vermieden.
Nginx-Konfiguration
Damit Nginx die Anfragen an die neue index-memcached.php weitergibt und die bisherige index.php zumindest für Gäste außer acht lässt, muss die Server konfiguration angepasst werden. Dies geschieht in der VirtualHost-Konfiguration der jeweiligen WordPress-Installation. Im server {…}-Abschnitt folgenden Code ergänzen:
#caching set $cache_flags ""; # Is this a mobile browser? if ($http_user_agent ~* "(2.0 MMP|240x320|400X240|AvantGo|BlackBerry|Blazer|Cellphone|Danger|DoCoMo|Elaine/3.0|EudoraWeb|Googlebot-Mob set $cache_flags "{$cache_flags}M"; } # Is the user logged in? if ($http_cookie ~* "(comment_author_|wordpress_logged_in_|wp-postpass_)" ) { set $cache_flags "${cache_flags}C"; } # Do we have query arguments? if ($is_args) { set $cache_flags "${cache_flags}Q"; } # If none of the rules above were matched, we can use the cache if ($cache_flags = "") { set $index_file /index-memcached.php; #must set to index-memcached.php for using cache } # Otherwise we'll just use index.php if ($cache_flags != "") { set $index_file /index.php; } location / { index $index_file index.html; try_files $uri $uri/ $index_file?$args; } # Force /wp-admin requests to always use index.php location /wp-admin/ { index index.php; try_files $uri $uri/ /index.php?$args; }
Die Arbeitsweise: nur wenn ein Desktop-User die Seite aufruft und zudem nicht das Dashboard aufruft wird die entsprechende Seite aus dem Cache ausgeliefert. Bei einer Responsive-Seite, die den selben Code sowohl für Desktop- als auch Mobile-Geräte verwendet, kann man den entsprechenden Abschnitt auskommentieren.
Fazit
Was hat der Aufwand nun gebracht? Ein Test mit webpagetest.org bringt folgendes Ergebnis: die Time-to-first-byte liegt bei 0.092s, die Zeit bis zur komplett geladenen Webseite liegt bei 0.993s. Dies entspricht einem Speed Index von 600.
An dieser Lösung problematisch: minify mittels W3 Total Cache funktioniert nicht, um defer und async für Javascript zu ermöglichen muss dies in der entsprechenden WordPress-Datei direkt hinterlegt werden.