WordPressの「死角」を突く:post_content_filteredによる検索負荷の極限分散戦略
WordPressのデータベース設計において、`wp_posts`テーブルの`post_content`カラムは「諸悪の根源」になり得ます。数メガバイトに及ぶHTMLタグ、ショートコード、そしてインラインCSS。これらが混在した巨大なTEXT型カラムに対し、`LIKE`句で全文検索をかける……それがどれほど無謀な行為か、貴殿なら理解しているはずです。
MySQLのバッファプールを食いつぶし、CPUをスパイクさせるクエリを投げるのは今日で終わりにしましょう。今回は、WordPressコアに古くから用意されているものの、ほとんど活用されていない「隠し玉」――`post_content_filtered`カラムを活用した、検索負荷分散のアーキテクチャを伝授します。
—
1. なぜ post_content_filtered なのか?
`post_content_filtered`は、本来リビジョン管理やコンテンツの加工結果を保持するために設計されたカラムです。ここに「検索用に最適化・正規化されたプレーンテキスト」を格納することで、以下のメリットが生まれます。
1. I/O負荷の劇的な低減: `post_content`から不要なHTMLタグやショートコードを除去した「純粋なテキスト」を別カラムに保持することで、検索対象のデータ量を削減し、インデックス効率を高めます。
2. 外部エンジンへの橋渡し: このカラムを「正規化済みデータ」のステートとして定義しておけば、ElasticsearchやMeilisearch等の外部検索エンジンへ同期する際の「クリーンなソース」として利用可能です。
3. コアの作法に従う: カスタムテーブルを作成して管理する手法もありますが、移行時のスキーマ不整合のリスクを考慮すれば、コアのテーブル構造を最大限活用するのが最も堅牢な設計です。
—
2. 堅牢な同期戦略:save_postフックの罠を回避する
単純な`save_post`フックでの更新は危険です。無限ループや、REST API経由の更新、WP-CLI実行時の予期せぬ挙動を考慮しなければなりません。以下のコードは、保守性を担保したプロダクションレベルの同期パターンです。
/
- コンテンツを正規化し、post_content_filteredへ同期する設計
- パフォーマンスを考慮し、wp_insert_post_dataフィルターを利用する
/
add_filter(‘wp_insert_post_data’, function ($data, $postarr) {
// コンテンツが更新されていない場合はスキップ
if (!isset($data[‘post_content’]) || ($postarr[‘post_content’] === $data[‘post_content’])) {
return $data;
}
// 1. HTML/ショートコードを除去し、検索に適したプレーンテキストを抽出
$raw_content = $data[‘post_content’];
$clean_text = wp_strip_all_tags(strip_shortcodes($raw_content));
// 2. 正規化(改行の統一、不要な空白の削除など)
$clean_text = preg_replace(‘/\s+/’, ‘ ‘, $clean_text);
// 3. カラムにセット
$data[‘post_content_filtered’] = trim($clean_text);
return $data;
}, 10, 2);
この実装の意図:
- フィルターの選定: `save_post`(アクション)ではなく`wp_insert_post_data`(フィルター)を使うのが肝です。データベースに書き込まれる直前のデータを操作するため、余計なSQL発行(UPDATE文)を1回減らすことができます。これが大規模サイトにおけるDB負荷軽減の定石です。
—
3. パフォーマンス最適化:インデックスの最適化
いくらカラムを分離しても、MySQLのインデックスが機能していなければ意味がありません。デフォルトでは`post_content_filtered`にはインデックスが貼られていません。
MySQLの標準的なB-Treeインデックスでは長大なテキストの全文検索は不可能です。実務では以下のいずれかのアプローチを推奨します。
1. FULLTEXTインデックスの追加:
ALTER TABLE wp_posts ADD FULLTEXT INDEX ft_content_filtered (post_content_filtered);
これを行うことで、`WHERE MATCH(post_content_filtered) AGAINST(‘キーワード’)`が利用可能になり、検索速度が数桁向上します。
2. 外部エンジンとの連携(推奨):
`post_content_filtered`が更新されたタイミングで、その値をトリガーとして外部検索エンジン(Algolia等)に非同期ジョブを投げます。これにより、MySQLはトランザクション処理に専念させ、検索という「計算負荷の高い処理」を完全に切り離すことが可能です。
—
4. 開発者への提言:なぜこれが「美しい設計」なのか
私がこの手法を推奨するのは、「WordPressのコア構造を汚さないから」です。
多くのエンジニアは、カスタムテーブルを作成してデータを同期させようとします。しかし、それはプラグインのアップデートやWordPressのメジャーバージョンアップ時に、予期せぬ互換性エラーやマイグレーションの複雑化を招きます。
`post_content_filtered`という「既に用意された器」を正しく正規化パイプラインの一部として組み込む。これこそが、WordPressのアーキテクチャを理解した上で、将来的な技術負債を最小化する唯一無二の解なのです。
次のアクション
貴殿が今すぐ行うべきは、本番環境の`wp_posts`テーブルのデータ量を計測し、`post_content_filtered`に正規化データを一括変換するWP-CLIコマンドを作成することです。その後、`wp_insert_post_data`フックをデプロイし、新規投稿に対して自動同期を走らせる。
この一連のフローが完了した瞬間、貴殿の検索クエリは「遅い処理」から「最適化されたインフラ」へと進化を遂げるはずです。コードは常にシンプルに、しかし内部構造には深くコミットせよ。以上。