WordPress-Performance mit nginx Microcaching steigern
Veröffentlicht: – Kommentar hinterlassen Letzte Aktualisierung:
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";
Code-Sprache: Nginx (nginx)
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;
Code-Sprache: Nginx (nginx)
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;
Code-Sprache: Nginx (nginx)
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;
}
Code-Sprache: Nginx (nginx)
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;
Code-Sprache: Nginx (nginx)
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.

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)
Code-Sprache: CSS (css)
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)
Code-Sprache: CSS (css)
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.