WordPressの検索負荷を根絶する:`post_content_filtered` を活用した全文検索エンジン同期アーキテクチャ
WordPressのデフォルト検索は、`wp_posts` テーブルに対する `LIKE` 句の多用という、スケーラビリティの観点からは「悪夢」とも言える実装に基づいている。数万件規模を超えた瞬間、MySQLのCPU使用率は急上昇し、インデックスが効かない `post_content` への部分一致検索はデータベースを破壊する。
本稿では、WordPressコアの隠し玉である `post_content_filtered` カラムを、外部全文検索エンジン(ElasticsearchやAlgoliaなど)との「同期用ハブ」として再定義し、検索負荷をデータベースの外へ完全にオフロードするアーキテクチャを伝授する。
—
1. なぜ `post_content_filtered` なのか?
WordPressコアのデータベース定義において、`post_content_filtered` は長らく「未使用」というレッテルを貼られてきた。しかし、我々のようなシステムアーキテクトにとって、これは「外部システムとのステートを管理するための唯一の予約席」である。
- 分離されたコンテキスト: `post_content` は純粋な投稿データとして扱い、`post_content_filtered` には検索エンジンに送るためのメタデータ、正規化済みテキスト、あるいは同期済みフラグ(ハッシュ値)を保持させる。
- データベース負荷の排除: 検索クエリをすべて外部エンジンへ投げれば、MySQLは「読み取り専用のストレージ」としての本来の役割に専念できる。
—
2. 堅牢な同期アーキテクチャの実装
`save_post` フックで同期を行う際、最も陥りやすい罠は「無限ループ」と「同期の不整合」だ。これを避けるため、`post_content_filtered` に検索エンジン側のインデックスIDや最終同期ハッシュを保持し、変更時のみ同期を走らせる設計を行う。
プロダクションコード:同期ハンドラー
/
- 投稿保存時に外部全文検索エンジンへの同期をトリガーする
/
class SearchEngineSyncManager {
public static function init() {
// 優先度100で実行し、他のプラグインの処理が終わった後の「最終状態」を捉える
add_action(‘save_post’, [self::class, ‘sync_to_external_engine’], 100, 3);
}
public static function sync_to_external_engine($post_ID, $post, $update) {
// 自動保存やリビジョンは無視する
if (wp_is_post_autosave($post) || wp_is_post_revision($post)) return;
// コンテンツのハッシュ値を計算して変更を検知
$content_hash = md5($post->post_content);
$current_meta = $post->post_content_filtered;
// 前回の同期時とコンテンツが同じならスキップ
if ($current_meta === $content_hash) return;
// 外部APIへ非同期リクエストを送信 (wp_remote_postは非同期化を推奨)
$result = self::push_to_search_engine($post);
if ($result) {
// ステートを更新: post_content_filtered にハッシュを書き込む
remove_action(‘save_post’, [self::class, ‘sync_to_external_engine’], 100);
wp_update_post([
‘ID’ => $post_ID,
‘post_content_filtered’ => $content_hash
]);
add_action(‘save_post’, [self::class, ‘sync_to_external_engine’], 100, 3);
}
}
private static function push_to_search_engine($post) {
// ここにElasticsearch等のREST APIクライアントを実装する
// 例: $client->index($post->post_title, $post->post_content);
return true;
}
}
SearchEngineSyncManager::init();
—
3. なぜこの設計が美しいのか?
1. 冪等性(Idempotency)の確保: `post_content_filtered` にハッシュ値を保持させることで、たとえ複数回フックが発火しても、APIを叩くのは内容が変化した時のみ。無駄なネットワークI/Oを排除できる。
2. コア機能の汚染回避: `postmeta` テーブルを肥大化させることなく、投稿オブジェクト自体にステートを持たせているため、クエリの結合コストが発生しない。
3. 保守性の向上: `remove_action` を用いた再帰防止処理は、WordPressのフックシステムを理解していれば当然の作法だ。これを記述しないコードは、往々にしてインデックスの無限生成を引き起こす。
—
4. 運用上の注意点とパフォーマンスの極意
- 非同期キューの検討: 上記コードはシンプルにするため同期実行しているが、実運用では `Action Scheduler` や `WP-Cron` を使い、APIリクエストをキューイングすべきだ。同期が詰まると管理画面の保存処理がタイムアウトする。
- 検索エンジンの死活監視: 外部エンジンがダウンしている場合、`post_content_filtered` の更新を止めるべきか否か。私の設計では、あえて「同期失敗フラグ」を別のメタとして保持し、WP-CLIでの再同期コマンドを別途実装する。
結論:MySQLを「検索エンジン」として扱う時代は終わった
WordPressは、適切に設計すればエンタープライズなスケールにも耐えうる強力なCMS基盤だ。しかし、それは「デフォルトの設定に甘んじない」ことが前提となる。
`post_content_filtered` という、かつて忘れ去られていたフィールドを「ステート管理の要」として再利用する。これこそが、アーキテクチャを知り尽くしたエンジニアが取るべき、最もエレガントな最適化の形だ。
さあ、あなたの環境の `wp_posts` テーブルから、泥臭い `LIKE` 検索を今すぐ追い出す準備を始めよう。