やりたいこと
- WordPress を動かしている Apache HTTP Server (Version 2.4) で mod_cache (mod_cache_disk) を設定したい。
- WordPress は mod_rewrite で Pretty Permalinks を設定している。
- リクエスト先(一覧 or 記事など)によってキャッシュの可否・有効期限を変更したい。
分かったこと
- すべてのリクエスト(レスポンス)に対するキャッシュは rewrite 後のURL (
/index.php
) のキャッシュとして扱われる。 - よって、特定のURLやディレクトリをキャッシュさせたい場合でも
CacheEnable
の対象に/index.php
が含まれている必要がある。 - rewrite 後のURL (
/index.php
) をリクエストされたURL毎にユニークになるようにしなければならない(そうしないとキャッシュされる内容がすべて同一になる)。 -
Location
ディレクティブなどを使ってキャッシュの設定やレスポンスヘッダを振り分けることができないので、キャッシュの可否・有効期限の制御は WordPress 側でやる。
Apache のデバッグログ: http://localhost:80/141412
へのリクエストに対して、最終的に Headers and body for URL http://localhost:80/index.php? cached.
となっている。
[cache:debug] [pid 16] cache_storage.c(664): [client 172.20.0.8:59718] AH00698: cache: Key for entity /141412?(null) is http://localhost:80/141412?, referer: https://localhost/
[cache:debug] [pid 16] mod_cache.c(210): [client 172.20.0.8:59718] AH00750: Adding CACHE_SAVE filter for /141412, referer: https://localhost/
[cache:debug] [pid 16] mod_cache.c(220): [client 172.20.0.8:59718] AH00751: Adding CACHE_REMOVE_URL filter for /141412, referer: https://localhost/
[cache:debug] [pid 16] cache_storage.c(664): [client 172.20.0.8:59718] AH00698: cache: Key for entity /index.php?(null) is http://localhost:80/index.php?, referer: https://localhost/
[cache_disk:debug] [pid 16] mod_cache_disk.c(572): [client 172.20.0.8:59718] AH00709: Recalled cached URL info header http://localhost:80/index.php?, referer: https://localhost/
[cache_disk:debug] [pid 16] mod_cache_disk.c(885): [client 172.20.0.8:59718] AH00720: Recalled headers for URL http://localhost:80/index.php?, referer: https://localhost/
[cache:debug] [pid 16] cache_util.c(735): [client 172.20.0.8:59718] AH00782: Cache lock obtained for stale cached URL, revalidating entry: /index.php, referer: https://localhost/
[cache:debug] [pid 16] cache_storage.c(359): [client 172.20.0.8:59718] AH00695: Cached response for /index.php isn't fresh. Adding conditional request headers., referer: https://localhost/
[cache:debug] [pid 16] mod_cache.c(210): [client 172.20.0.8:59718] AH00750: Adding CACHE_SAVE filter for /index.php, referer: https://localhost/
[cache:debug] [pid 16] mod_cache.c(220): [client 172.20.0.8:59718] AH00751: Adding CACHE_REMOVE_URL filter for /index.php, referer: https://localhost/
[cache:debug] [pid 16] mod_cache.c(1332): [client 172.20.0.8:59718] AH00769: cache: Caching url: /index.php, referer: https://localhost/
[cache:debug] [pid 16] mod_cache.c(1338): [client 172.20.0.8:59718] AH00770: cache: Removing CACHE_REMOVE_URL filter., referer: https://localhost/
[cache_disk:debug] [pid 16] mod_cache_disk.c(1350): [client 172.20.0.8:59718] AH00737: commit_entity: Headers and body for URL http://localhost:80/index.php? cached., referer: https://localhost/
設定例
mod_cache
# CacheRoot は `a2enmod cache_disk` で mod_cache_disk を有効にすると勝手に設定される模様
# see: /etc/apache2/mods-available/cache_disk.conf
CacheEnable disk /
CacheDisable /wp-admin/
CacheDisable /wp-content/
CacheDisable /wp-includes/
# Set-Cookie がある場合はキャッシュしない
CacheIgnoreHeaders Set-Cookie
# リクエストに no-cache があってもキャッシュを返す
CacheIgnoreCacheControl On
mod_rewrite
/index.php
に ?cache_key=%{REQUEST_URI}
のようにリクエストされたURLをクエリとして付加することで、別のURLとして扱ってくれる。
<Directory "/var/www/html">
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php?cache_key=%{REQUEST_URI} [QSA,L]
</Directory>
修正 (2018-01-22)
/index.php?cache_key=%{REQUEST_URI}
への rewrite は、 ?cache_key=
によってクエリが上書かれてしまうので、 QSA
フラグを付ける必要がある。1
ref: https://httpd.apache.org/docs/2.4/rewrite/flags.html#flag_qsa
WordPress
テーマの functions.php かプラグインで、以下のように設定する。(上記の設定の場合、Expires
や Cache-Control
ヘッダーを付加しない限りキャッシュされない)
// 'send_headers' フックだと is_single() などが使えない。
add_action('template_redirect', function () {
if (is_user_logged_in()) {
return;
}
if (is_single()) {
header('Cache-Control: s-maxage=60');
}
});
クエリ付きのURLは CacheDefaultExpire
や CacheMinExpire
の値は反映されない模様。
If the URL included a query string (e.g. from a HTML form GET method) it will not be cached unless the response specifies an explicit expiration by including an "Expires:" header or the max-age or s-maxage directive of the "Cache-Control:" header, as per RFC2616 sections 13.9 and 13.2.1.
(https://httpd.apache.org/docs/2.4/en/caching.html)
結果
CacheDetailHeader On
とすることで、X-Cache-Detail
ヘッダーで詳細を確認できる。
Cache-Control ヘッダーを設定したレスポンス
1回目 (cache miss)
2回目 (cache hit)
Apache のデバッグログ: http://localhost:80/141412
へのリクエストに対して、最終的に Headers and body for URL http://localhost:80/index.php?cache_key=/141412 cached.
となっている。
[cache:debug] [pid 17] cache_storage.c(664): [client 172.20.0.8:51730] AH00698: cache: Key for entity /141412?(null) is http://localhost:80/141412?, referer: https://localhost/
[cache:debug] [pid 17] mod_cache.c(210): [client 172.20.0.8:51730] AH00750: Adding CACHE_SAVE filter for /141412, referer: https://localhost/
[cache:debug] [pid 17] mod_cache.c(220): [client 172.20.0.8:51730] AH00751: Adding CACHE_REMOVE_URL filter for /141412, referer: https://localhost/
[cache:debug] [pid 17] cache_storage.c(664): [client 172.20.0.8:51730] AH00698: cache: Key for entity /index.php?cache_key=/141412 is http://localhost:80/index.php?cache_key=/141412, referer: https://localhost/
[cache_disk:debug] [pid 17] mod_cache_disk.c(572): [client 172.20.0.8:51730] AH00709: Recalled cached URL info header http://localhost:80/index.php?cache_key=/141412, referer: https://localhost/
[cache_disk:debug] [pid 17] mod_cache_disk.c(885): [client 172.20.0.8:51730] AH00720: Recalled headers for URL http://localhost:80/index.php?cache_key=/141412, referer: https://localhost/
[cache:debug] [pid 17] cache_util.c(735): [client 172.20.0.8:51730] AH00782: Cache lock obtained for stale cached URL, revalidating entry: /index.php?cache_key=/141412, referer: https://localhost/
[cache:debug] [pid 17] cache_storage.c(359): [client 172.20.0.8:51730] AH00695: Cached response for /index.php isn't fresh. Adding conditional request headers., referer: https://localhost/
[cache:debug] [pid 17] mod_cache.c(210): [client 172.20.0.8:51730] AH00750: Adding CACHE_SAVE filter for /index.php, referer: https://localhost/
[cache:debug] [pid 17] mod_cache.c(220): [client 172.20.0.8:51730] AH00751: Adding CACHE_REMOVE_URL filter for /index.php, referer: https://localhost/
[cache:debug] [pid 17] mod_cache.c(1332): [client 172.20.0.8:51730] AH00769: cache: Caching url: /index.php?cache_key=/141412, referer: https://localhost/
[cache:debug] [pid 17] mod_cache.c(1338): [client 172.20.0.8:51730] AH00770: cache: Removing CACHE_REMOVE_URL filter., referer: https://localhost/
[cache_disk:debug] [pid 17] mod_cache_disk.c(1350): [client 172.20.0.8:51730] AH00737: commit_entity: Headers and body for URL http://localhost:80/index.php?cache_key=/141412 cached., referer: https://localhost/
Cache-Control ヘッダーを設定していないレスポンス
キャッシュされない。
User-Agent によって表示を分けている場合
WordPress 側で User-Agent によって表示を出し分けている場合は、Apache にキャッシュさせる内容も分ける必要がある。その場合、/index.php
に渡すクエリを User-Agent (表示内容)によって変わるようにすることで対応できる。
(mod_cache は Vary
ヘッダーを見ているので、Vary
ヘッダーに User-Agent
を追加することでキャッシュを分けることもできるが、無数にある User-Agent 毎にキャッシュが生成されてしまうため、キャッシュの効果が薄れる)
# デバイスの判定・出し分けのロジックはWordPress側と合わせる(以下は簡略版)
BrowserMatchNoCase ".*" layout=desktop
BrowserMatchNoCase "(ipod|iphone)" layout=mobile
BrowserMatchNoCase "android.*mobile" layout=mobile
<Directory "/var/www/html">
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# この場合 %{ENV:layout} には 'desktop' or 'mobile' が渡される
RewriteRule . /index.php?cache_key=%{REQUEST_URI}:%{ENV:layout} [QSA,L]
</Directory>
このとき、WordPress側でクライアントキャッシュも有効にすると(max-age
など)、同一ブラウザにおいて User-Agent を変更した場合に、同じクライアントキャッシュが使い回されてしまう。これを防ぐためには Vary
ヘッダーに User-Agent
を追加する必要があるが、前述の通りキャッシュの意味がなくなる。
(途中で User-Agent を変更するのは開発時くらいだから無視してもいいかもしれないが、モバイルブラウザの「PC版サイトをリクエスト」とかも影響あるかも)
備考
- Apache のその他の設定や、WordPress の挙動、テーマやプラグインによっては、独自にレスポンスヘッダーを追加してることもあるので気をつける。
- mod_cache は細かい制御が難しいので、リアルタイム性が求められるコンテンツやユーザーによって変化するコンテンツが含まれている場合はしんどいかも。
参考
- https://httpd.apache.org/docs/2.4/en/caching.html
- https://httpd.apache.org/docs/2.4/en/mod/mod_cache.html
- Apacheのキャッシュ(mod_cache、mod_disk_cache)+WordPressの注意点
-
この場合、クエリの違い毎にキャッシュが生成されるので、キャッシュ内容に関係ないクエリについては
CacheIgnoreURLSessionIdentifiers
等で工夫する。 ↩