transient で外部APIの結果をキャッシュする方法|get_transient が false のとき

結論

  • 外部APIなど時間のかかる処理は、get_transient() で確認し、false なら取得し直して set_transient() で保存する、という形が基本です。
  • transient の有効期限は「最大の寿命」で、最低保証はありません。公式にも、期限前に消えることがあると書かれています。消えても動く(再取得できる)コードにします。
  • 取得結果が false や空文字になりうる場合は、そのまま保存すると「キャッシュなし」と区別できません。配列に包んで保存します。
  • transient の名前は172文字以内にします(公式の記載)。

原因・仕組み

transient は、有効期限付きの一時データです。外部オブジェクトキャッシュ(Memcached など)が無い環境では、wp_options テーブルに _transient_名前 と _transient_timeout_名前 の2行で保存されます。外部オブジェクトキャッシュがあると、DBではなくメモリに保存され、wp_options には現れません。

項目 内容
保存 set_transient( $name, $value, $expiration_seconds )
取得 get_transient( $name )(期限切れ・未保存は false)
削除 delete_transient( $name )
保存先 オブジェクトキャッシュがなければ wp_options
期限 最大寿命。最低保証なし

期限切れの行は、取得されたタイミングなどで削除されます。大量に溜まるとオプションテーブルを圧迫するため、不要になったら delete_transient() を呼びます。

手順(サンプルコード)

function myplug_get_rates() {
    $key  = 'myplug_rates_v1';
    $data = get_transient( $key );
    if ( false !== $data ) {
        return $data['rates'];                    // キャッシュあり
    }

    $res = wp_remote_get( 'https://api.example.com/rates', array( 'timeout' => 5 ) );
    if ( is_wp_error( $res ) || 200 !== wp_remote_retrieve_response_code( $res ) ) {
        return array();                           // 失敗は短時間だけ保存してもよい
    }
    $rates = json_decode( wp_remote_retrieve_body( $res ), true );
    if ( ! is_array( $rates ) ) {
        return array();
    }

    // 配列に包んで保存(false・空でもキャッシュと区別できる)
    set_transient( $key, array( 'rates' => $rates ), 15 * MINUTE_IN_SECONDS );
    return $rates;
}

出力する値は、キャッシュから取り出した後でも、必ずエスケープします(esc_html( $rates['USD'] ) など)。外部の値は信頼できないデータです。

動作確認(検証環境と結果)

WordPress 7.1.2(ja)、PHP 8.2.12、MariaDB 10.4.32。php run.php transient-vs-option.php を実行しました。外部API呼び出し(wp_remote_get)自体はネットワークを使うため実行していません(未検証)。

ext object cache: NULL
array(1) { ["a"]=> int(1) }
_transient_timeout_t_demo autoload=off
_transient_t_demo autoload=off
missing: bool(false)
stored false: string(0) ""
after expiry: bool(false)
len 172: set=true get='v'
len 180: set=true get='v'
len 200: set=true get='v'
  • 外部オブジェクトキャッシュがない環境では、_transient_t_demo と _transient_timeout_t_demo の2行が wp_options に作られ、autoload は off でした。
  • 未保存のキーは false でした。
  • false を保存して取得すると、空文字 '' が返りました。false !== $data で判定すると「キャッシュあり」と見なされ、空文字を返してしまいます。配列で包む理由はここです。
  • 1秒で期限切れにして2秒待つと、false に戻りました。
  • 180文字、200文字の名前でも、この環境では保存・取得できました。ただし公式は172文字までとしているため、長い名前に依存しないでください。レビュー時の再実行では、172文字超の名前の行が option_name(191文字)で切り詰められたまま delete_transient() で消えずに残り、その状態で同じスクリプトを再実行すると、172文字の名前の取得が false になることがありました(切り詰められた行と名前が衝突したためと考えられます)。

transient で外部APIの結果をキャッシュする方法の注意点

  • 名前に $_GET の値などを使うと、transient が無限に増えます。キー数に上限を設けるか、使わない設計にします。
  • ユーザーごとに内容が違うデータは、get_current_user_id() をキーに含めるか、キャッシュしません。
  • 期限を 0 にすると、期限なし(オプションと同様に自動読み込み)になります。大きなデータでは避けます。
  • 外部APIが落ちたときに、毎回リクエストを投げ続けないよう、失敗も短い期限で保存する方法もあります。ただし、その場合は成功時のキャッシュと区別できるキー・値にします。
  • プラグインを削除するとき、delete_transient() で後始末をします。

transient で外部APIの結果をキャッシュする方法でよくあるミス

  • get_transient() の結果を if ( $data ) で判定し、空配列や 0 をキャッシュなしとして扱う。
  • 取得失敗の結果(エラー)を、長時間キャッシュしてしまう。
  • 「期限までは必ず残る」と考え、再取得のコードを書かない。
  • 名前に日付や変数を入れ続け、古いtransientが溜まる。

transient で外部APIの結果をキャッシュする方法のチェックリスト

  • キャッシュが無い場合の再取得処理が書かれているか。
  • false・空値のケースで、正しく判定できるか。
  • 名前は固定の接頭辞付きで、172文字以内か。
  • 取得した外部データを、出力時にエスケープしているか。

transient で外部APIの結果をキャッシュする方法のFAQ(よくある質問)

Q. transient とオプションはどちらを使うべき?
A. 消えても再生成できるデータは transient、消えると困る設定値はオプションです。

Q. 期限が来る前に更新したいときは?
A. delete_transient() で削除するか、set_transient() で上書きします。

Q. マルチサイトで共有するには?
A. set_site_transient() を使います。

筆者の見解(transient で外部APIの結果をキャッシュする方法)

transient は「速くするための一時的なメモ」であって、「データの置き場」ではないと考えます。消える前提で、再生成の処理を先に書き、そのあとでキャッシュを足すと、バグの少ない作りになります。false を保存しない、という一点だけでも、守る価値があると思います。

transient で外部APIの結果をキャッシュする方法の関連項目

出典(一次情報)

タイトルとURLをコピーしました