これまでは WP Fastest Cache というプラグインを使って WordPress のページキャッシュを返していたんですが、「クエリパラメータを無視してキャッシュを返したい」という要件が出てきたため、WP Fastest Cache を使ったやり方では対応できなくなってしまいました。
「クエリパラメータを無視する」というのは、
https://foo.com/bar?p=a
https://foo.com/bar?p=b
https://foo.com/bar?p=c
というリクエストに対してすべて同じキャッシュを返したい、という話になります。
こんな構成になってます。
インターネット
↓
Cloud Load Balancing
↓
Cloud CDN
↓
nginx
↓
FastCGI (WordPress)
↓
WP Fastest Cache
↓
動的にページを生成
この一番下にある「動的にページを生成」という部分が処理としてかなり重いため、その手前の部分でキャッシュを返したい、という状況です。
WP Fastest Cache に「クエリパラメータを無視してキャッシュを返す」というオプションがあればそれで終了なんですが、どうやらそういうオプションはなさそうだったので nginx 側でキャッシュするようにしました。
画像ファイル等の静的ファイルをキャッシュする用途としては Cloud CDN を使っています。
ただ、index.php を通して返すレスポンスについては、「WordPress の管理画面にログインしている場合はキャッシュを使わない」「ログインしていない場合はキャッシュを使う」という制御を行いたかったので、そのあたりの細かい制御ができる nginx を使うことにしました。
WordPress の管理画面にログインしている状態の Cookie を確認したところ、
wordpress_logged_in_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
というキーを見つけたので、これを使って判定することにしました(XXXX... の部分はランダムな文字列が入っています)
まず、map ディレクティブを使って変数を設定します。
map $http_cookie $is_wordpress_logged_in {
default 0;
~*wordpress_logged_in 1;
}
これで「$http_cookie の中に wordpress_logged_in という文字列があれば $is_wordpress_logged_in を 1 に設定する。文字列が見つからなければ 0 に設定する」という処理になります。
~*
の部分は「大文字小文字を無視して正規表現マッチを行う」という意味になります。分かりづらい。
default
はそのままですね。
さきほど map ディレクティブで設定した $is_wordpress_logged_in を使います。
以下のように書くと、
fastcgi_cache_bypass $is_wordpress_logged_in;
「$is_wordpress_logged_in が 0 のときはキャッシュを使う。それ以外の値の場合はキャッシュを無視する」という設定になります。
今回は FastCGI を使っているケースのため fastcgi_cache_bypass ディレクティブで指定していますが、FastCGI じゃない場合は代わりに proxy_cache_bypass を使う形になります。
fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=test:10m max_size=1g inactive=60m;
これで、
という設定になります。
こんな感じでキャッシュのキーを設定します。
fastcgi_cache_key "$scheme$request_method$host$tmp_uri";
キーの値が同じなら同じキャッシュファイルが返されます。
ここの設定に今回つまづいてしまって、最初、
fastcgi_cache_key "$scheme$request_method$host$uri";
と書いたところ、どの URL を開いてもすべて同じページが返ってくる、という状態になってしまいました。
原因を調べるため、
add_header X-Cache-Key "$scheme$request_method$host$uri";
と書いて、レスポンスヘッダにキャッシュキーが載ってくるようにしました。
実際にキャッシュキーを確認したところ、$uri の部分が毎回 /index.php
となっていることが分かったんですね。
要するに、WordPress の場合、最終的にすべて index.php にリクエストを流すため、FastCGI が $uri を見るタイミングでは毎回 /index.php
になってしまっている、という状態だったわけです。
そこで、index.php に処理を流す直前の $uri の値を、
location / {
set $tmp_uri $uri;
try_files $uri $uri/ /index.php?$args;
}
という感じで $tmp_uri という変数に保存しておいて、キャッシュキーの方では $uri ではなく $tmp_uri を参照する形に書き換えました。
こんな感じです。
fastcgi_cache_key "$scheme$request_method$host$tmp_uri";
これで「毎回同じページが返される」という問題は解決しました。
また、$uri にはクエリパラメータを除外した値が入ってくるため、当初やりたかった「クエリパラメータの内容を無視してキャッシュを返す」という部分も実現できたわけです。
どのキャッシュを使うのかを指定します。
fastcgi_cache_path の keys_zone で設定した名前を指定します。
fastcgi_cache test;
キャッシュを使うか使わないか切り替えるため、前に宣言した $is_wordpress_logged_in をここで使います。
fastcgi_cache_bypass $is_wordpress_logged_in;
たとえば以下のように書くと、
fastcgi_cache_valid 200 10m;
という設定になります。
たとえば以下のように書くと、
fastcgi_cache_methods GET;
という設定になります。
以下のように書いておくと、
add_header X-Cache-Status $upstream_cache_status;
レスポンスヘッダの X-Cache-Status に MISS, HIT, BYPASS 等のステータスが設定されます。
このステータスを見ながら、意図した通りにキャッシュが効いてるかどうかテストする、という感じですね。
これまで書いたことをすべてまとめると、nginx の設定ファイルは以下のようになります。
※ 今回の話と関係ない部分は省略してあります
http {
map $http_cookie $is_wordpress_logged_in {
default 0;
~*wordpress_logged_in 1;
}
fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=test:10m max_size=1g inactive=60m;
fastcgi_cache_key "$scheme$request_method$host$tmp_uri";
server {
add_header X-Cache-Status $upstream_cache_status;
location / {
set $tmp_uri $uri;
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
...
fastcgi_cache test;
fastcgi_cache_bypass $is_wordpress_logged_in;
fastcgi_cache_valid 200 10m;
fastcgi_cache_methods GET;
...
}
}
}
以上!