【実務・中級編】カスタム投稿タイプとタクソノミーのキャッシュ:Redisを活用した高速な階層構造の取得 – WordPress 内部コア・データベース構造とパフォーマンス最適化解析バイブル

階層タクソノミーの呪縛を解く:Redisによる「O(1)」検索アーキテクチャの構築

WordPressの `get_terms()` を再帰的に呼び出し、階層構造を構築するために `wp_get_term_children()` を何度も叩く——。もし君がそんなコードをプロダクション環境で動かしているなら、今すぐその手を止めるべきだ。

WordPressのタクソノミーテーブル(`wp_term_taxonomy`)は、JOINと再帰の嵐だ。データ量が増大するにつれ、MySQLのクエリプランナは悲鳴を上げ、`wp_cache` のオブジェクトキャッシュだけでは、複雑な階層構造の再構築コストを完全にはカバーできない。

今日は、Redisを単なる「KVストア」としてではなく、「タクソノミーのフラット化インデックス」として活用し、階層データをO(1)で取得する設計パターンを伝授する。

—

1. なぜWordPress標準のキャッシュでは不十分なのか

`get_terms()` は内部的に `WP_Term_Query` を呼び出し、結果を `wp_cache_set` する。しかし、階層構造を保持したツリー状の配列を動的に生成する場合、毎回 `parent` フィールドを照合して親子関係を再帰的に繋ぎ直す必要がある。

この「再帰的構築」こそが、CPUとメモリを浪費する真犯人だ。解決策はシンプルだ。「構築済みツリーをシリアライズしてRedisに置く」。これに尽きる。

—

2. 堅牢な実装:`TaxonomyTreeBuilder` パターン

バグを埋め込まないための鉄則は、「DB更新時にキャッシュをパージするイベント駆動設計」である。`clean_term_cache` をフックし、整合性を担保する。

以下に、実務でそのまま使える堅牢なクラス設計を示す。

  • TaxonomyTreeCache: 階層タクソノミーをフラット化してRedisに永続化する
  • /
    class TaxonomyTreeCache {
    private const CACHE_KEY = ‘app_tax_tree_%s’; // %s = taxonomy_name

    public static function get_tree(string $taxonomy): array {
    $key = sprintf(self::CACHE_KEY, $taxonomy);
    $cached = wp_cache_get($key, ‘tax_cache’);

    if (false !== $cached) {
    return $cached;
    }

    // キャッシュがない場合、構築して保存
    $tree = self::build_recursive_tree($taxonomy);
    wp_cache_set($key, $tree, ‘tax_cache’, HOUR_IN_SECONDS);

    return $tree;
    }

    private static function build_recursive_tree(string $taxonomy, int $parent = 0): array {
    $terms = get_terms([‘taxonomy’ => $taxonomy, ‘parent’ => $parent, ‘hide_empty’ => false]);
    $branch = [];

    foreach ($terms as $term) {
    $branch[] = [
    ‘term_id’ => $term->term_id,
    ‘name’ => $term->name,
    ‘children’ => self::build_recursive_tree($taxonomy, $term->term_id)
    ];
    }
    return $branch;
    }

    // 更新時のキャッシュ破棄(ここが設計の肝)
    public static function purge_cache($term_id, $taxonomy) {
    wp_cache_delete(sprintf(self::CACHE_KEY, $taxonomy), ‘tax_cache’);
    }
    }

    // フックの登録
    add_action(‘created_term’, [‘TaxonomyTreeCache’, ‘purge_cache’], 10, 2);
    add_action(‘edited_term’, [‘TaxonomyTreeCache’, ‘purge_cache’], 10, 2);
    add_action(‘delete_term’, [‘TaxonomyTreeCache’, ‘purge_cache’], 10, 2);

    —

    3. パフォーマンス最適化の極意

    このコードをプロダクションで運用する際、以下の3点に注意を払う必要がある。これこそが「動くコード」と「運用に耐えるシステム」の境界線だ。

    ① プリフェッチの戦略

    `build_recursive_tree` 内で何度も `get_terms` を呼ぶのは避けるべきだ。理想は、タクソノミーの全データを一度のクエリで取得し、PHP側でハッシュマップ(連想配列)を作成して、リンクを繋ぎ変える「単一パスでのツリー構築」だ。再帰回数を減らすことが、メモリ消費の抑制に繋がる。

    ② Redisのシリアライズコスト

    巨大なタクソノミーの場合、`serialize()` は重い。もし階層が数千単位に及ぶなら、JSONへの変換や、Redisのハッシュ型(`HSET`)を用いて構造を保存する手法も検討せよ。WordPressの `wp_cache_set` はシリアライズを自動で行うが、データサイズが大きすぎるとRedisの処理時間がボトルネックになる。

    ③ 整合性とRace Condition

    `created_term` フックでのパージは重要だが、トラフィックが激しいサイトでは「パージしてから再構築されるまでの間」に大量のリクエストがDBを叩く「キャッシュ雪崩(Cache Stampede)」が起きる。
    これを防ぐには、「キャッシュのパージ時に、即座に非同期処理(Action Schedulerなど)でキャッシュを再構築する」のがプロの解法だ。

    —

    最後に:エンジニアへの提言

    WordPressにおいて「データベースへのアクセスを減らす」ことは、単なる高速化ではない。それはシステムの寿命を延ばすことと同義だ。

    今回紹介したアプローチは、Redisという強力な武器を使い、DBへの負荷を最小限に抑えつつ、アプリケーションのレスポンスをミリ秒単位で短縮する。設計の際は、「なぜキャッシュが消えるのか」「どうすればキャッシュミス時のコストを最小化できるか」を常に自問自答してほしい。

    コードは書くだけなら誰でもできる。「スケールを前提とした堅牢な設計」こそが、君たちを「ただのコーダー」から「真のエンジニア」へ引き上げる。次のレビューでは、このアーキテクチャを堂々と提案してくれ。期待している。

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