
「またこのページか……」
管理している WordPress サイトに、表示のたびに一呼吸待たされるページがあります。
WordPress は、同じ仕事を毎回やり直す
原因は、外部 API からのデータ取得がリクエストのたびに走っていることでした。WordPress は律儀で、同じページを開かれるたびに、同じ処理を最初からやり直します。結果が1時間変わらない API 問い合わせでも、毎回実行される。表示時間は、そのぶん延びていきます。
結果を取っておいて、期限まで使い回す
重い処理の結果に有効期限をつけて保存し、期限内は使い回します。基本形はこれだけです。
$events = get_transient( 'upcoming_events' );
if ( false === $events ) {
// キャッシュがないときだけ、重い処理を実行する
$events = fetch_events_from_api();
set_transient( 'upcoming_events', $events, HOUR_IN_SECONDS );
}
get_transient() で保存済みのデータを探し、なければ(false が返ってきたら)重い処理を実行して、set_transient() で結果を保存しておく。次にページが開かれたときは、保存済みのデータを返すだけです。API への問い合わせは走りません。
transient は英語で「つかのま」という意味です。有効期限は秒で渡し、期限が切れると取得できなくなる。上の例の HOUR_IN_SECONDS は1時間で、DAY_IN_SECONDS や WEEK_IN_SECONDS といった定数も用意されています。これで WordPress は、1時間に一度だけ働けば済むようになります。
向くデータと、向かないデータ
効くのは「誰が見ても同じで、多少古くても困らない」データです。
- 外部 API のレスポンス(イベント情報、天気、SNS の埋め込みなど)
- 重い WP_Query の結果(人気記事ランキング、関連記事の一覧など)
- 集計に時間がかかる値(投稿数、レビューの平均点など)
逆に、ユーザーごとに内容が変わるものや、常に最新でなければ困るもの(在庫数、価格、決済まわり)には使いません。
この線引きは技術の問題ではありません。古い値を見せてしまったときに、誰がどう困るか。それを決めるのは業務の側です。判断がつかないうちはキャッシュしない、で構いません。遅いページより、間違った値のほうが高くつきます。
期限を待つのではなく、更新の瞬間に消す
キャッシュの悩みは、いつまで信じてよいかです。イベント情報のように日々変わるものなら1時間。めったに変わらないものなら1日でも1週間でもかまいません。
ただし期限を延ばすほど速くなり、延ばすほど「たったいま更新したのに、表示が古いままだ」という声が近づいてきます。この二つは、期限の長さをどう選んでも同時には満たせません。
抜け道があります。データが変わった瞬間にキャッシュを消してしまえばいい。
add_action( 'save_post_event', function () {
delete_transient( 'upcoming_events' );
} );
save_post_event は、投稿タイプ event の保存時に走るフックです(save_post_{投稿タイプ} 形式)。イベントが保存されたら、古いキャッシュを消す。次にページが開かれたときに重い処理がもう一度だけ走り、新しい結果が保存し直されます。
こうすると、有効期限の役割が変わります。鮮度を保つ主役は更新フックのほうで、期限は「フックを書き忘れた経路があったときの保険」になる。だから期限は長めに取っておいて構いません。表示が古いままになる時間は、更新の瞬間にゼロへ戻ります。
エラーにならない落とし穴
Transients でつまずくとき、たいてい画面にエラーは出ません。動いているように見えて、期待した結果になっていない。次の四つは、どれも気づきにくい部類です。
有効期限に 0 を渡さない
第3引数を省略したときも 0 と同じ扱いです。0 は無期限の意味になりますが、このとき WordPress はそのデータを autoload、つまりどのページの表示でも毎回読み込むようになります。速くするつもりで置いたデータが、すべてのページを少しずつ遅くしていきます。期限は必ず入れてください。
期限切れのデータは、すぐには消えない
期限が切れたデータは、次に get_transient() で取りに来たときに期限切れと判定され、そこで初めて削除されます。WordPress 4.9 以降は1日1回の cron(delete_expired_transients())でも掃除されますが、cron はサイトへの訪問がないと動きません。期限が切れた瞬間に消えるわけではない、と覚えておいてください。
キー名は 172 文字まで
WordPress は保存時に _transient_ と _transient_timeout_ の接頭辞を付けて wp_options に書き込みます。option_name 列が varchar(191) なので、長いほうの接頭辞 19 文字を引いた 172 文字が上限です。超えると片方の名前だけが切り詰められ、対になるはずのデータが噛み合わなくなります。エラーも警告も出ません。URL のような長い文字列からキーを作るときは、'events_' . md5( $url ) のように畳んでおくと安全です。
保存先を当てにしない
普段は wp_options テーブルに保存されますが、Redis や Memcached といったオブジェクトキャッシュが有効なサイトでは、自動的にそちらへ保存されます。コードは一行も変えなくてよい、という設計です。裏を返せば、Transients を「必ずそこにあるもの」として当てにしてはいけません。オブジェクトキャッシュはサーバーの再起動で飛びますし、期限より早く追い出されることもあります。
つまり Transients が保証しているのは「速いこと」だけで、「あること」ではありません。false が返ってきたら作り直す。あの if 文が、いつでも本体です。