【実務・中級編】wp_postsのpost_content_filteredカラムを活用した検索負荷の分散と全文検索エンジンとの連携 – WordPress 内部コア・データベース構造とパフォーマンス最適化解析バイブル

エンジニア諸君、今日も`wp_options`の肥大化や、不毛な`LIKE`検索によるスロークエリと戦っているようだな。

多くの開発者がWordPressの検索機能を拡張する際、安易に「外部検索プラグインを入れる」か、あるいは「`WP_Query`の`s`パラメータをそのまま使う」という二択に逃げる。しかし、プロフェッショナルならば、まずはデータベーススキーマの物理構造を深く洞察すべきだ。

今回は、`wp_posts`テーブルにひっそりと鎮座し、コアのコードでもほとんど活用されていない「眠れる遺産」、`post_content_filtered`カラムを、検索パフォーマンスを劇的に向上させる「検索専用バッファ」へと転生させる設計思想を伝授する。

—

1. なぜ `post_content` を直接検索してはいけないのか

現在のWordPressにおいて、`post_content`はもはや「純粋なテキスト」ではない。Gutenberg(ブロックエディタ)の登場により、内部は複雑なJSON風のHTMLコメントや、レンダリングが必要なショートコードで溢れかえっている。

この状態で `LIKE ‘%キーワード%’` を走らせると何が起きるか?
1. ノイズの混入: ブロックの属性値(`{“align”:”center”}`等)まで検索対象になり、精度の低い結果が返る。
2. インデックスの無効化: 前方一致すら使えない中間一致検索は、レコード数が増えるほど線形的にレスポンスを悪化させる。
3. 計算リソースの無駄: 検索のたびに、DBエンジンは膨大なHTMLタグを舐めるようにスキャンせねばならない。

ここで我々が活用すべきが、`post_content_filtered` だ。

—

2. 物理構造の再定義: `post_content_filtered` の役割

`wp_posts` テーブルの物理設計を思い出してほしい。`post_content_filtered` は、本来「加工済みのコンテンツ(Markdownから変換されたHTMLなど)」を保持するために用意されたカラムだが、現在のコアではほぼ未使用だ。

このカラムを、「検索エンジン・外部API向けの、ノイズを排除したプレーンテキスト・キャッシュ」として再定義する。

設計のメリット

  • 検索精度の向上: ショートコードやHTMLタグを完全に除去した「純粋なテキスト」のみを格納できる。
  • 同期コストの最小化: `post_content` と同じテーブルにあるため、外部DB(Elasticsearch等)への同期フラグとしても機能し、JOINの必要がない。
  • 検索クエリの軽量化: 検索対象をこのカラムに絞ることで、スキャン対象のデータサイズを大幅に削減できる。

—

3. 実装:自動プレ処理とカラムへの格納戦略

まずは、投稿保存時に `post_content` をクリーニングし、`post_content_filtered` に自動同期するロジックを実装する。ここで `save_post` フックを使うのは二流だ。`wp_insert_post_data` を使い、DBに書き込まれる直前のデータをインターセプトするのが最も効率的で堅牢だ。

/

  • 投稿保存時に検索専用のクリーンなテキストを作成し、
  • post_content_filteredカラムに格納する。

/
add_filter(‘wp_insert_post_data’, function (array $data, array $postarr) {
// 自動保存やリビジョン、特定の投稿タイプを除外(設計に応じて調整)
if (defined(‘DOING_AUTOSAVE’) && DOING_AUTOSAVE) return $data;
if ($data[‘post_status’] === ‘inherit’) return $data;

// 1. ブロックをパースし、プレーンテキストのみを抽出
// do_blocks() を通すことで、動的ブロックの出力内容も考慮可能だが、
// 重い場合は strip_tags(render_block()) 等で調整する。
$content = $data[‘post_content’];

// 2. タグの除去とデコード
// ショートコードも解決したい場合は do_shortcode() を挟む
$clean_text = wp_strip_all_tags($content);
$clean_text = strip_shortcodes($clean_text);

// 3. 検索精度を上げるための正規化(全角半角、大文字小文字など)
$clean_text = mb_convert_kana($clean_text, ‘rnkKV’); // 必要に応じて
$clean_text = mb_strtolower($clean_text, ‘UTF-8’);

// 4. フィルタリング済みカラムに格納
$data[‘post_content_filtered’] = $clean_text;

return $data;
}, 10, 2);

—

4. 検索エンジンのハイジャック: `posts_search` フックの活用

次に、フロントエンドの検索クエリが走った際、`post_content` ではなく `post_content_filtered` を見に行くようにSQLを書き換える。

/

  • 標準の検索対象を post_content から post_content_filtered に切り替える

/
add_filter(‘posts_search’, function ($search, \WP_Query $query) {
if (is_admin() || !$query->is_main_query() || !$query->is_search()) {
return $search;
}

global $wpdb;

// 標準の検索SQL構文: AND (((wp_posts.post_title LIKE ‘%…%’) OR (wp_posts.post_content LIKE ‘%…%’)))
// これを置換して post_content_filtered を見るように変更する
$search = str_replace(
“{$wpdb->posts}.post_content LIKE”,
“{$wpdb->posts}.post_content_filtered LIKE”,
$search
);

return $search;
}, 10, 2);

—

5. 発展:外部検索エンジン(Elasticsearch)との連携基盤

この設計の真価は、外部検索エンジンとの連携で発揮される。
例えば Elasticsearch や Algolia へデータを飛ばす際、通常は「HTMLをパースして、不要なデータを除いて…」という重い処理を非同期ジョブ側で行う必要がある。

しかし、この設計ならば 「`post_content_filtered` が更新されたら、その中身をそのまま検索エンジンに Put するだけ」 という極めてシンプルな実装に落とし込める。

/

  • 外部検索エンジン(例: Elasticsearch)への同期トリガー

/
add_action(‘save_post’, function ($post_id, $post, $update) {
if (!$update || $post->post_status !== ‘publish’) return;

// すでに前処理済みのデータが DB に入っているため、取得して飛ばすだけ
$search_optimized_content = $post->post_content_filtered;

// 非同期リクエスト(wp_remote_post等)で外部エンジンに同期
// MySearchEngine::sync($post_id, $search_optimized_content);

}, 10, 3);

—

6. パフォーマンス上の注意点と「プロの詰め」

この設計を導入する際、以下の2点を必ず確認せよ。

1. 既存レコードの更新: このロジックを導入しただけでは、過去の投稿の `post_content_filtered` は空のままだ。WP-CLI を使い、全投稿を `wp_update_post()` するスクリプトを一度走らせる必要がある。
2. インデックスの検討: もし `post_content_filtered` に対して頻繁に DB レベルの検索を行うなら、MySQLの FULLTEXT INDEX をこのカラムに付与することを検討しろ。`post_content` 全体に貼るよりも、ノイズが少ない分インデックスサイズも劇的に小さく済む。

結論

システムのボトルネックは、常に「不透明なデータ構造」にある。
WordPressが提供するスキーマを「ただそこにあるもの」として使うのではなく、その物理的な特性を理解し、「用途に応じたデータの正規化バッファ」として再定義する。

`post_content_filtered` の活用は、その第一歩に過ぎない。
コードレビューで「なぜ `post_content` をそのまま検索してはいけないのか?」と問われたら、胸を張ってこの設計思想を語りたまえ。

健闘を祈る。

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