2026年8月26日

2026年8月26日

WordPressのTransientキャッシュが正しく動作しない問題を解決する方法

はじめに

WordPressのset_transient()でデータを保存したのにget_transient()falseを返す・transientの有効期限が設定した時間より早く切れる・Redisなどの外部オブジェクトキャッシュを導入したらtransientの動作が変わった・大量のtransientがwp_optionsテーブルを肥大化させている・サイトtransientとtransientの使い分けがわからないといった問題は、transientの仕組みと外部キャッシュとの関係を理解することで解決できます。

症状・原因

  • set_transient()のキー名に使用できない文字(172文字超・スペース等)が含まれている
  • 外部オブジェクトキャッシュ(Redis・Memcached)使用時、transientはDBではなくキャッシュに保存される
  • wp_optionsテーブルに保存される場合、期限切れtransientは次回アクセス時まで自動削除されない
  • サイト全体に共通のtransientにはset_site_transient()を使う必要がある(マルチサイト)

解決手順

ステップ1:Transientの状態を診断する

# transientの件数と保存先を確認
wp eval "
\$test_key = 'wp_doctor_test_' . time();
set_transient(\$test_key, 'test_value', 60);
\$val = get_transient(\$test_key);
echo 'Transient works: ' . (\$val === 'test_value' ? 'YES' : 'NO') . PHP_EOL;
delete_transient(\$test_key);

// 外部キャッシュの確認
echo 'Object cache: ' . (wp_using_ext_object_cache() ? 'External' : 'DB') . PHP_EOL;
"

# DBに保存されているtransientの件数
wp db query "SELECT COUNT(*) FROM wp_options WHERE option_name LIKE '_transient_%';"

# 上位10件の大きなtransient
wp db query "
SELECT option_name, LENGTH(option_value) AS size
FROM wp_options
WHERE option_name LIKE '_transient_%'
  AND option_name NOT LIKE '_transient_timeout_%'
ORDER BY size DESC
LIMIT 10;"

ステップ2:Transientを正しく使う

// ① 基本的な使い方
function get_my_data(): array {
    $cache_key = 'my_plugin_data_v1';  // バージョン付きキー
    $cached    = get_transient($cache_key);

    if ($cached !== false) {
        return $cached;  // キャッシュヒット
    }

    // 重い処理(APIコール・複雑なDBクエリなど)
    $data = fetch_heavy_data();

    // 1時間キャッシュ(HOUR_IN_SECONDS = 3600)
    set_transient($cache_key, $data, HOUR_IN_SECONDS);

    return $data;
}

// ② キャッシュの無効化(データ更新時)
function update_my_data(array $new_data): void {
    save_data($new_data);
    delete_transient('my_plugin_data_v1');  // キャッシュクリア
}

// ③ キー名のルール(172文字以内・英数字とアンダースコア推奨)
$key = 'my_data_' . md5(serialize($query_args));  // 動的キーにはハッシュを使う

ステップ3:外部オブジェクトキャッシュとの併用

// Redis/Memcached使用時の注意点
// transientはオブジェクトキャッシュ経由になりwp_optionsには保存されない

// ① wp_cache_* で直接オブジェクトキャッシュを操作
function get_cached_posts(int $user_id): array {
    $cache_key   = "user_posts_{$user_id}";
    $cache_group = 'my_plugin';

    $cached = wp_cache_get($cache_key, $cache_group);
    if ($cached !== false) {
        return $cached;
    }

    $posts = get_posts(['author' => $user_id, 'numberposts' => 10]);
    wp_cache_set($cache_key, $posts, $cache_group, 30 * MINUTE_IN_SECONDS);

    return $posts;
}

// ② 投稿更新時にキャッシュをクリア
add_action('save_post', function(int $post_id): void {
    $author_id = (int) get_post_field('post_author', $post_id);
    wp_cache_delete("user_posts_{$author_id}", 'my_plugin');
});

// ③ 外部キャッシュ使用中かどうかを確認してから処理分岐
if (wp_using_ext_object_cache()) {
    // Redisなどに直接保存(TTLが効く)
    wp_cache_set('my_key', $data, 'group', DAY_IN_SECONDS);
} else {
    // DBに保存(transient)
    set_transient('my_key', $data, DAY_IN_SECONDS);
}

ステップ4:サイトTransientとマルチサイト対応

// マルチサイトでネットワーク全体に共通のキャッシュを使う場合
// set_transient はサイトごと、set_site_transient はネットワーク全体

function get_network_data(): array {
    // サイトtransient(ネットワーク全体で共有)
    $cached = get_site_transient('network_config');
    if ($cached !== false) {
        return $cached;
    }

    $data = fetch_network_config();
    set_site_transient('network_config', $data, DAY_IN_SECONDS);

    return $data;
}

// プラグイン更新チェックもサイトtransientを使用
// wp_update_plugins → _site_transient_update_plugins に保存される

// マルチサイトで全サブサイトのtransientを一括削除
if (is_multisite()) {
    $sites = get_sites(['number' => 0, 'fields' => 'ids']);
    foreach ($sites as $site_id) {
        switch_to_blog($site_id);
        wp_cache_flush();  // そのサイトのキャッシュをクリア
        restore_current_blog();
    }
}

ステップ5:Transientの期限管理と一括削除

# 期限切れのtransientを削除
wp transient delete --expired

# 全transientを削除(キャッシュリセット)
wp transient delete --all

# 特定プレフィックスのtransientを削除
wp eval "
global \$wpdb;
\$deleted = \$wpdb->query(
    \"DELETE FROM \$wpdb->options
     WHERE option_name LIKE '_transient_my_plugin_%'
        OR option_name LIKE '_transient_timeout_my_plugin_%'\"
);
echo \"Deleted: {\$deleted} rows\" . PHP_EOL;
"

# テーブルを最適化して断片化を解消
wp db query "OPTIMIZE TABLE wp_options;"
// プラグイン無効化時にtransientを自動削除
register_deactivation_hook(__FILE__, function(): void {
    global $wpdb;
    $wpdb->query(
        "DELETE FROM {$wpdb->options}
         WHERE option_name LIKE '_transient_my_plugin_%'
            OR option_name LIKE '_transient_timeout_my_plugin_%'"
    );
});

注意事項

  • set_transient()の有効期限に0を渡すとキャッシュが期限なしになります。外部オブジェクトキャッシュではサーバー再起動まで保持、DBでは永続的に残ります
  • 外部オブジェクトキャッシュ使用時はwp transient delete --expiredでDBの期限切れtransientは削除されますが、Redis側のキャッシュはサーバーのTTLで管理されます

まとめ

WordPress Transientキャッシュ問題の解決は①wp_using_ext_object_cache()で保存先を確認・set_transientget_transientの動作テスト・DBのtransient件数を確認、②キー名に172文字制限・動的キーにはmd5(serialize())でハッシュ化・get_transient() !== falseでキャッシュヒット判定、③外部キャッシュ使用時はwp_cache_get/setを直接使用・グループ指定でキャッシュを管理・save_postフックでキャッシュ無効化、④マルチサイトでネットワーク共通データはset_site_transient()を使用・switch_to_blogで各サイトのキャッシュを個別クリア、⑤wp transient delete --expiredで期限切れ削除・プラグイン無効化時にdeactivation_hookで自動削除・OPTIMIZE TABLEでテーブルを最適化の手順で解決します。

お気軽にご相談ください

お見積りへ お問い合わせへ