2026年7月26日
2026年7月26日
WordPressのTransientキャッシュを使った高速化テクニック
はじめに
WordPressのTransient APIは、データベースへの繰り返しクエリを削減するシンプルなキャッシュ機構です。重いWP_QueryやAPI呼び出しの結果をキャッシュすることで、ページロード時間を大幅に改善できます。さらに、Redis/Memcachedを使ったObject Cacheと組み合わせることで、複数サーバー環境でも一貫したキャッシュが実現できます。本記事では、クラスベースのキャッシュ設計から本番環境対応のRedis連携まで解説します。
症状・原因
キャッシュが必要になる典型的なパフォーマンス問題:
- 複雑なWP_Queryが毎リクエストで実行され、MySQLがボトルネックになっている
- 外部API(天気情報・為替レートなど)への呼び出しがレスポンスを遅延させている
- 集計クエリ(SUM/COUNT/GROUP BY)がページロードのたびに実行されている
- 大量の投稿を処理するアーカイブページで "504 Gateway Timeout" が発生する
- New Relicや Query Monitor でスロークエリが多数検出されている
解決手順
ステップ1:Transient APIの基本操作
set_transient、get_transient、delete_transientの基本的な使い方を押さえます。
<?php
/**
* 基本的なTransient APIの使い方
*/
// キャッシュを取得(存在しない場合は false を返す)
$featured_posts = get_transient('my_featured_posts');
if ($featured_posts === false) {
// キャッシュが存在しない → DBからデータを取得
$query = new WP_Query([
'post_type' => 'post',
'posts_per_page' => 10,
'meta_key' => '_is_featured',
'meta_value' => '1',
'orderby' => 'date',
'order' => 'DESC',
]);
$featured_posts = $query->posts;
wp_reset_postdata();
// 1時間(3600秒)キャッシュに保存
set_transient('my_featured_posts', $featured_posts, HOUR_IN_SECONDS);
}
// キャッシュを手動で削除(投稿が更新されたとき)
function my_clear_featured_posts_cache(int $post_id): void {
if (get_post_type($post_id) !== 'post') {
return;
}
delete_transient('my_featured_posts');
}
add_action('save_post', 'my_clear_featured_posts_cache');
add_action('deleted_post', 'my_clear_featured_posts_cache');
// 時間定数の参照
// MINUTE_IN_SECONDS = 60
// HOUR_IN_SECONDS = 3600
// DAY_IN_SECONDS = 86400
// WEEK_IN_SECONDS = 604800
// MONTH_IN_SECONDS = 2592000 (30日)
// YEAR_IN_SECONDS = 31536000
ステップ2:バージョンキーを使ったグループ無効化
複数のキャッシュを一括無効化するバージョンキーパターンを実装します。
<?php
/**
* バージョンキーを使ったキャッシュグループの管理
* カテゴリーが更新されたとき、そのカテゴリー関連の全キャッシュを一括無効化する
*/
class My_Cache_Manager {
private const CACHE_PREFIX = 'my_plugin_';
private const VERSION_KEY = 'my_plugin_cache_version';
private const DEFAULT_TTL = DAY_IN_SECONDS;
/**
* 現在のキャッシュバージョンを取得する
*/
private static function get_version(): string {
$version = get_transient(self::VERSION_KEY);
if ($version === false) {
$version = (string) time();
set_transient(self::VERSION_KEY, $version, YEAR_IN_SECONDS);
}
return $version;
}
/**
* バージョン付きキャッシュキーを生成する
*
* @param string $key ベースキー
* @return string バージョン付きキャッシュキー
*/
public static function make_key(string $key): string {
$version = self::get_version();
// MD5で短縮(WordPressのoptionsテーブルはキー長制限あり: 191文字)
return self::CACHE_PREFIX . md5($version . '_' . $key);
}
/**
* キャッシュを取得する
*
* @param string $key キャッシュキー
* @return mixed キャッシュされた値、または false
*/
public static function get(string $key): mixed {
return get_transient(self::make_key($key));
}
/**
* キャッシュを保存する
*
* @param string $key キャッシュキー
* @param mixed $value 保存する値
* @param int $ttl 有効期限(秒)
*/
public static function set(string $key, mixed $value, int $ttl = self::DEFAULT_TTL): bool {
return set_transient(self::make_key($key), $value, $ttl);
}
/**
* グループ全体を一括無効化する(バージョンを更新するだけで全キーが無効化される)
*/
public static function flush_group(): void {
$new_version = (string) time();
set_transient(self::VERSION_KEY, $new_version, YEAR_IN_SECONDS);
}
}
// カテゴリー更新時に全グループキャッシュを無効化
function my_flush_cache_on_term_change(int $term_id): void {
My_Cache_Manager::flush_group();
}
add_action('edited_term', 'my_flush_cache_on_term_change');
add_action('delete_term', 'my_flush_cache_on_term_change');
ステップ3:リクエスト内Object Cacheで二重クエリを防ぐ
wp_cache_get/setを使ったリクエストスコープのキャッシュを実装します。
<?php
/**
* wp_cache_get/set を使ったリクエスト内キャッシュ
* 同一リクエスト内で同じデータを複数箇所で参照する場合に有効
*/
function my_get_user_permissions(int $user_id): array {
$cache_key = 'user_permissions_' . $user_id;
$cache_group = 'my_plugin_permissions';
// Object Cache から取得(同一リクエスト内ではメモリから返る)
$permissions = wp_cache_get($cache_key, $cache_group);
if ($permissions !== false) {
return $permissions;
}
// データベースから取得(重いクエリ)
global $wpdb;
$permissions = $wpdb->get_results($wpdb->prepare(
"SELECT pm.meta_key, pm.meta_value
FROM {$wpdb->usermeta} pm
WHERE pm.user_id = %d
AND pm.meta_key LIKE 'my_plugin_perm_%'",
$user_id
), ARRAY_A);
// Object Cache に保存(TTLなし = リクエスト終了まで)
// Redis/Memcached が有効な場合は永続化される
wp_cache_set($cache_key, $permissions, $cache_group, HOUR_IN_SECONDS);
return $permissions ?: [];
}
/**
* WP_Query 結果をTransientとObject Cacheの両方でキャッシュするラッパー
*
* @param string $cache_key キャッシュキー
* @param array $query_args WP_Queryの引数
* @param int $ttl Transientの有効期限
* @return WP_Post[] 投稿配列
*/
function my_cached_query(string $cache_key, array $query_args, int $ttl = HOUR_IN_SECONDS): array {
// 1. Object Cache を確認(最速)
$posts = wp_cache_get($cache_key, 'my_plugin_queries');
if ($posts !== false) {
return $posts;
}
// 2. Transient を確認(DBから読むが軽量)
$posts = get_transient($cache_key);
if ($posts !== false) {
wp_cache_set($cache_key, $posts, 'my_plugin_queries');
return $posts;
}
// 3. 実際のWP_Queryを実行(最も重い)
$query = new WP_Query(array_merge(['no_found_rows' => true], $query_args));
$posts = $query->posts;
wp_reset_postdata();
// 両方のキャッシュに保存
set_transient($cache_key, $posts, $ttl);
wp_cache_set($cache_key, $posts, 'my_plugin_queries', $ttl);
return $posts;
}
ステップ4:期限切れTransientのクリーンアップ
DBに溜まった期限切れTransientを定期削除する処理を実装します。
<?php
/**
* 期限切れTransientをデータベースから削除する
* WP-Cronと組み合わせて定期実行することを推奨
*/
function my_cleanup_expired_transients(): int {
global $wpdb;
// Object Cache (Redis/Memcached) を使用している場合はoptionsテーブルに書き込まれないため不要
if (wp_using_ext_object_cache()) {
return 0;
}
// 期限切れのtransient_timeoutを取得して関連データを削除
$expired_timeouts = $wpdb->get_col(
"SELECT option_name FROM {$wpdb->options}
WHERE option_name LIKE '_transient_timeout_%'
AND option_value < UNIX_TIMESTAMP(NOW())
LIMIT 500"
);
$deleted = 0;
foreach ($expired_timeouts as $timeout_key) {
// _transient_timeout_xxx → _transient_xxx
$transient_key = str_replace('_transient_timeout_', '_transient_', $timeout_key);
$wpdb->delete($wpdb->options, ['option_name' => $timeout_key]);
$wpdb->delete($wpdb->options, ['option_name' => $transient_key]);
$deleted++;
}
// optionsテーブルのオートロード最適化
if ($deleted > 0) {
$wpdb->query("OPTIMIZE TABLE {$wpdb->options}");
}
error_log("[my_plugin] 期限切れTransient削除: {$deleted}件");
return $deleted;
}
// WP-Cron に登録して毎日実行
add_action('my_plugin_cleanup_transients', 'my_cleanup_expired_transients');
if (!wp_next_scheduled('my_plugin_cleanup_transients')) {
wp_schedule_event(time(), 'daily', 'my_plugin_cleanup_transients');
}
ステップ5:Redisを使った永続Objectキャッシュの設定
Redis Object Cacheプラグインの設定とカスタムキャッシュの実装例を示します。
<?php
// wp-config.php に追加(Redis Object Cache プラグインと組み合わせる)
define('WP_REDIS_HOST', '127.0.0.1');
define('WP_REDIS_PORT', 6379);
define('WP_REDIS_DATABASE', 0);
define('WP_REDIS_PREFIX', 'mysite_'); // マルチサイトで重複を防ぐ
define('WP_REDIS_MAXTTL', 86400); // 最大TTL: 24時間
// Redis が有効化された後のキャッシュ使用例
function my_get_expensive_data(string $query_type): array {
$cache_key = 'expensive_' . $query_type . '_' . date('Ymd');
$cache_group = 'my_plugin';
// Redis が有効なら wp_cache_get は Redis から読む(超高速)
$data = wp_cache_get($cache_key, $cache_group);
if ($data !== false) {
return $data;
}
// 重いデータ処理(外部API呼び出しなど)
$response = wp_remote_get('https://api.example.com/data?type=' . urlencode($query_type), [
'timeout' => 10,
'headers' => ['Accept' => 'application/json']
]);
if (is_wp_error($response)) {
// エラー時は空配列をキャッシュしてリトライストームを防ぐ
wp_cache_set($cache_key, [], $cache_group, 5 * MINUTE_IN_SECONDS);
return [];
}
$data = json_decode(wp_remote_retrieve_body($response), true) ?? [];
// Redis に15分間キャッシュ(TTLはRedisが管理するので自動削除される)
wp_cache_set($cache_key, $data, $cache_group, 15 * MINUTE_IN_SECONDS);
return $data;
}
# Redis Object Cache プラグインのインストール(WP-CLI)
wp plugin install redis-cache --activate
# Redis 接続テスト
wp redis status
# キャッシュのフラッシュ
wp cache flush
# Transientの一覧確認
wp transient list --format=table
# 期限切れTransientの手動削除
wp transient delete --expired
注意事項
get_transient()が返すfalseは「キャッシュなし」を意味するため、false自体をキャッシュしたい場合は値をラップする(例:['value' => false])- キャッシュキーの最大長はWordPressの制限により172文字のため、長いキーは
md5()で短縮すること - Transientはマルチサイトで
get_site_transient()/set_site_transient()を使わないと各サイトのDBに個別保存される - Redis/Memcachedが有効な場合、Transient APIは自動的にObject Cacheに委譲されるため
optionsテーブルを汚染しない - キャッシュのパージ(無効化)ロジックを設計する際は、キャッシュ依存関係を整理してから実装すること(依存関係が複雑になると整合性の保証が困難)
まとめ
WordPressのTransient APIは、set_transient/get_transientというシンプルなAPIで強力なキャッシュ機構を提供します。バージョンキーによるグループ無効化パターンを使うことで複雑なキャッシュ管理も安全に実装でき、Redisと組み合わせることで本格的な高速化が実現できます。定期的なキャッシュクリーンアップにはWP-Cronのスケジュール処理を組み合わせることで、データベースの肥大化を防げます。