Game Tsukuru Kun
Game Tsukuru Kun
@Twitter

WordPress のページキャッシュを nginx を使って行う方法

背景

これまでは 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 は使えないのか?

画像ファイル等の静的ファイルをキャッシュする用途としては Cloud CDN を使っています。

ただ、index.php を通して返すレスポンスについては、「WordPress の管理画面にログインしている場合はキャッシュを使わない」「ログインしていない場合はキャッシュを使う」という制御を行いたかったので、そのあたりの細かい制御ができる nginx を使うことにしました。

具体的な要件

「WordPress の管理画面にログインしているかどうか」を見抜く方法

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 はそのままですね。

WordPress の管理画面にログインしている場合はキャッシュを無視する

さきほど 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 まわりの設定

fastcgi_cache_path

fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=test:10m max_size=1g inactive=60m;

これで、

という設定になります。

fastcgi_cache_key

こんな感じでキャッシュのキーを設定します。

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

どのキャッシュを使うのかを指定します。

fastcgi_cache_path の keys_zone で設定した名前を指定します。

fastcgi_cache test;

fastcgi_cache_bypass

キャッシュを使うか使わないか切り替えるため、前に宣言した $is_wordpress_logged_in をここで使います。

fastcgi_cache_bypass $is_wordpress_logged_in;

fastcgi_cache_valid

たとえば以下のように書くと、

fastcgi_cache_valid 200 10m;

という設定になります。

fastcgi_cache_methods

たとえば以下のように書くと、

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;
            ...
        }
    }
}

以上!