Veröffentlicht am Schreib einen Kommentar

WordPress-Performance mit nginx Microcaching steigern

Diverse Caching-Plugins versprechen eine bessere Performance, indem sie den Inhalt von Seiten zwischenspeichern und danach die zwischengespeicherte Variante ausgeben, ohne dass die Daten erneut aus der Datenbank abgefragt werden müssen. Wer bei sich aber nginx einsetzt, kann ein ähnliches – wenn nicht sogar besseres – Ergebnis durch FastCGI-Microcaching herbeiführen.

Was ist FastCGI-Microcaching?

Wird eine Anfrage an den Server gestellt und zu PHP weitergereicht (wie es bei jedem regulären Aufruf einer WordPress-Seite der Fall ist), speichert nginx das Ergebnis dieser Anfrage zwischen und ergibt bei einer zweiten identischen Abfrage dieses zwischengespeicherte Ergebnis direkt zurück. Soweit, so identisch zu bekannten Caching-Plugins. Auf Ebene von nginx kann man dann Ausnahmen oder die Dauer des Cachings definieren.

Wie kann ich Microcaching aktivieren?

Zuerst muss ein Cache-Key definiert werden. Dieser bestimmt, auf Basis welcher Parameter eine Anfrage als identisch angesehen wird. Ich habe ihn folgendermaßen definiert:

fastcgi_cache_key "$scheme://$request_method$host$request_uri";

Als identisch wird die Anfrage dabei angesehen, wenn das Schema (bsp. HTTPS), die Anfragemethode (bsp. GET), der Host (bsp. epiph.yt) und der Pfad (bsp. /blog/) identisch sind. Dieser fastcgi_cache_key sollte global in der nginx.conf (im http-Kontext) definiert werden.

Als nächstes sollte man dem Microcaching anweisen, einige Header zu ignorieren:

fastcgi_ignore_headers Cache-Control Expires Vary;

Weiterhin muss der Pfad angegeben werden, unter dem die zwischengespeicherten Anfragen abgespeichert werden. Hier werden statische HTML-Dateien abgelegt.

fastcgi_cache_path /var/cache/nginx/fastcgi_cache levels=1:2 keys_zone=kittmedia:20m inactive=60m max_size=200m;

Hierbei wird zuerst der Pfad angegeben und dann noch weitere Parameter. Der Pfad muss von dem Benutzer, unter dem nginx ausgeführt wird, beschreibbar sein.

levels gibt die Datenstruktur an, unter der die Dateien abgespeichert werden.

keys_zone gibt an, unter welchem Schlüssel die Anfragen gespeichert werden. Unterschiedliche Websites erfordern hierbei unterschiedliche Schlüssel, da ansonsten Anfragen von Seite X im schlimmsten Fall mit einem zwischengespeicherten Ergebnis aus Seite Y beantwortet werden. Hinter dem Schlüsselnamen wird dessen Größe definiert. Auf diesen wird später noch referenziert.

inactive gibt an, nach welcher Zeit ein zwischengespeichertes Ergebnis gelöscht wird, egal ob es sich verändert hat. In diesem Fall steht der Wert auf 60 Minuten.

max_size gibt an, wie viel Speicher für zwischengespeicherte Ergebnisse maximal verwendet werden. Hier: 200 MiB.

Manche Anfragen sollten generell ignoriert und nicht im Microcache gespeichert werden:

set $no_cache 0;

if ($http_cookie ~* "comment_author_|wordpress_(?!test_cookie)|wp-postpass_") {
	set $no_cache 1;
}

if ($request_uri ~* "/(wp-admin/|xmlrpc.php|wp-login.php)") {
	set $no_cache 1;
}

if ($request_method = POST) {
	set $no_cache 1;
}

In diesem Fall heißt das: Sobald bestimmte Cookies gesetzt sind (die ein Indikator dafür sind, dass man angemeldet ist), man Inhalte unter /wp-admin/, /xmlrpc.php oder wp-login.php aufruft oder die Anfragemethode ein POST ist, wird die Variable $no_cache auf 1 gesetzt.

Zu guter Letzt muss das Microcaching nun noch in den FastCGI-Einstellungen definiert werden:

fastcgi_cache kittmedia;
fastcgi_cache_valid 200 60m;
fastcgi_cache_bypass $no_cache;
fastcgi_no_cache $no_cache;
fastcgi_cache_use_stale updating error timeout invalid_header http_500;
fastcgi_cache_lock on;
fastcgi_cache_lock_timeout 10s;

add_header X-Cache $upstream_cache_status;

fastcgi_cache definiert die oben definiere keys_zone, in der die Anfragen gespeichert werden.
fastcgi_cache_valid definiert, dass nur Anfragen, die als Antwort einen Status-Code 200 haben, zwischengespeichert werden, mit einer Dauer von 60 Minuten.
fastcgi_cache_bypass definiert, ob eine Anfrage nicht vom Cache bedient werden soll. In diesem Fall passiert das, wenn $no_cache auf 1 gesetzt wurde, wie bei den Abfragen oben definiert. Ähnliches gilt für fastcgi_no_cache, nur dass es definiert, dass solche Anfragen nicht selbst zwischengespeichert werden.
fastcgi_cache_use_stale definiert im Falle eines Fehlers, wann eine zwischengespeicherte Antwort ausgeliefert werden kann. In diesem Fall: wenn der Cache gerade aktualisiert wird, wenn es zu einem Fehler kommt, einer Zeitüberschreitung, ungültige Header vorhanden sind oder ein HTTP-500-Fehler auftritt.
fastcgi_cache_lock definiert, ob nur eine Anfrage gleichzeitig einen neuen Cache-Eintrag anlegen darf.
fastcgi_cache_lock_timeout definiert, wann eine Anfrage durch FastCGI (in unserem Fall also PHP) beantwortet wird, wenn eine Zeitüberschreitung auftritt. Hier: 10 Sekunden.

Über den add_header kann dann noch optional angegeben werden, ob das aktuelle Ergebnis eine zwischengespeicherte Variante ist oder nicht. Dann steht im Antwort-Header X-Cache HIT. Andernfalls X-Cache BYPASS.

Das war dann auch bereits die gesamte Konfiguration. Damit werden alle Anfragen von Gästen im Frontend zwischengespeichert, wenn sie keinen Kommentar geschrieben haben. In meinem Monitoring konnte ich sehr gut sehen, dass die Zeit, die eine Anfrage auf einer Website benötigt hatte, von durchschnittlich ~320 ms auf ~65 ms verringert werden konnte.

Anfragen von ~320 ms auf ~65 ms

Insbesondere die Wartezeit, die beispielsweise der Browser auf die Antwort einer Anfrage warten muss, wird hierbei drastisch reduziert.

Über ab – Apache HTTP server benchmarking tool kann man ebenfalls sehr gut prüfen, ob das Microcaching greift, um die WordPress-Performance zu verbessern. In meinem Fall sah das Ergebnis mit dem Befehl ab -c 10 -t 10 -k https://example.com/ folgendermaßen aus:

Ohne Microcaching

Requests per second:    11.89 [#/sec] (mean)
Time per request:       840.936 [ms] (mean)
Time per request:       84.094 [ms] (mean, across all concurrent requests)
Transfer rate:          293.39 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        7   17  13.0     12      58
Processing:   340  768 168.9    758    1207
Waiting:      333  760 168.9    754    1203
Total:        369  786 171.5    778    1264

Percentage of the requests served within a certain time (ms)
  50%    778
  66%    840
  75%    901
  80%    937
  90%   1016
  95%   1095
  98%   1153
  99%   1169
 100%   1264 (longest request)

Mit Microcaching

Requests per second:    359.25 [#/sec] (mean)
Time per request:       27.836 [ms] (mean)
Time per request:       2.784 [ms] (mean, across all concurrent requests)
Transfer rate:          14947.39 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        7   23  11.8     21      65
Processing:     1    4   4.5      3      38
Waiting:        0    3   4.2      1      35
Total:          7   28  13.4     25      78

Percentage of the requests served within a certain time (ms)
  50%     25
  66%     31
  75%     36
  80%     39
  90%     47
  95%     54
  98%     61
  99%     64
 100%     78 (longest request)

Wie man schon jeweils in der ersten Zeile erkennen kann, wird die Geschwindigkeit so stark erhöht, dass der Webserver statt 11,89 Anfragen pro Sekunde nun 359,25 Anfragen pro Sekunde beantworten kann. Und auch die Werte, die der Webserver zur Verarbeitung (Processing) benötigt und wie lange er auf Ergebnisse warten muss (Waiting), haben sich drastisch reduziert.

Microcaching ist übrigens nicht auf WordPress limitiert. Praktisch jede Website, deren Inhalte sich nicht für jeden Benutzer ändern, kann von FastCGI-Microcaching profitieren.

Und mein Shop?

Solange Warenkorb-Funktionalitäten und andere dynamische oder interaktive Funktionen rein mit JavaScript gelöst sind, kann Microcaching auch Websites mit einem Shop zumindest teilweise zwischenspeichern. Das gilt aber auch hier nur für Gäste. Inhalte von angemeldeten Benutzern können praktisch nicht zwischengespeichert werden, außer man möchte für jeden dieser Benutzer einen eigenen Cache aufbauen – was man nicht tun sollte.

Außerdem müssen Bestellprozesse und ähnliches davon ausgeschlossen werden. Mit entsprechender Konfiguration ist das durchaus möglich. Allerdings sollte das umfangreich getestet werden, bevor man dies live nimmt, um zu verhindern, dass Benutzer zwischengespeicherte Ergebnisse angezeigt bekommen, die sie nicht angezeigt bekommen sollten.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.