終焉を迎える「%LIKE%」:WP_Queryにおける検索の限界と、その低レイヤにおける技術的敗北
WordPressを単なるCMSとしてではなく、一つのアプリケーションランタイムとして捉えた時、我々が直面する最大のパフォーマンス・ボトルネックの一つが `WP_Query` の `s` パラメータ、すなわち標準のキーワード検索である。
初心者は「検索が重い」と嘆き、中級者は「プラグインで解決しよう」と試みる。しかし、システムの深淵を知るアーキテクトにとって、この問題はリレーショナルデータベースのB-Treeインデックス構造と、計算量 O(N) の線形走査という、コンピュータサイエンスの基礎に根ざした必然の帰結である。
今回は、なぜ `WP_Query` の `s` パラメータが大規模データにおいて破綻するのか、その内部メカニズムを解剖し、真の最適化とは何かを提示する。
—
1. WP_Query が生成する SQL の解剖:非効率の正体
`WP_Query` に `s` パラメータを渡した際、コア内部の `WP_Query::parse_search` メソッドが駆動する。このメソッドが生成するSQLは、概ね以下のような形となる。
SELECT wp_posts.
FROM wp_posts
WHERE 1=1
AND (((wp_posts.post_title LIKE ‘%keyword%’) OR (wp_posts.post_excerpt LIKE ‘%keyword%’) OR (wp_posts.post_content LIKE ‘%keyword%’)))
AND wp_posts.post_type = ‘post’
AND (wp_posts.post_status = ‘publish’)
ORDER BY wp_posts.post_date DESC
ここで注目すべきは `LIKE ‘%keyword%’` という記述だ。
中間一致(Leading Wildcard)の罪
MySQL(InnoDB)において、通常のインデックス(B-Tree)は「左側からのプレフィックス一致」に対してのみ有効に機能する。
`LIKE ‘keyword%’`(前方一致)であればインデックス・レンジ・スキャンが可能だが、`LIKE ‘%keyword%’`(中間・後方一致)となった瞬間、ストレージエンジンはB-Treeのリーフノードを端から端まで全て読み込む「フルインデックススキャン」、最悪の場合は「フルテーブルスキャン」を強制される。
2. メモリとI/Oの観点から見た「死」のメカニズム
なぜフルテーブルスキャンが致命的なのか。それは単に「時間がかかる」からではない。システムの共有リソースである InnoDB Buffer Pool を汚染するからだ。
1. Buffer Poolの汚染: 大規模な `wp_posts` テーブル(例:数十万行、数GB)に対して `LIKE ‘%s%’` を実行すると、MySQLは全データをディスクからメモリへ読み込もうとする。
2. LRUアルゴリズムの暴走: MySQLのBuffer PoolはLRU(Least Recently Used)アルゴリズムで管理されている。検索クエリが大量の「一度しか使わないデータ」をメモリにロードすることで、本来メモリに常駐すべき「頻繁にアクセスされるホットデータ」がメモリから追い出される(Eviction)。
3. システム全体の低速化: 結果として、検索以外の通常のページ表示や、他のクエリのディスクI/Oが急増し、システム全体のスループットが劇的に低下する。
これは単一クエリの遅延ではなく、アーキテクチャ全体の崩壊を意味する。
—
3. 計算量の罠:マルチワード検索の指数的負荷
`WP_Query` はデフォルトでスペース区切りのマルチワード検索をサポートしている。
例えば「WordPress 高速化」で検索した場合、SQLは以下のようになる。
AND (
(wp_posts.post_title LIKE ‘%WordPress%’ OR … )
AND
(wp_posts.post_title LIKE ‘%高速化%’ OR … )
)
この論理積(AND)条件が増えるたびに、CPUは各行に対して正規表現に近いパターンマッチングを繰り返し実行しなければならない。データ量が $N$、キーワード数が $M$ の場合、最悪計算量は $O(N \times M)$ に達し、CPUバウンドな処理がスレッドを占有し、MySQLのコネクションプールを食いつぶす。
—
4. 限界を突破する:アーキテクトが取るべき戦略
この問題を根本から解決するためには、`WP_Query` のデフォルトの挙動を「捨てる」覚悟が必要だ。
A. MySQL Full-Text Search (FTS) への移行
InnoDBはバージョン5.6以降、全文検索インデックスをサポートしている。`LIKE` ではなく `MATCH…AGAINST` を利用することで、インデックスを利用した高速な検索が可能になる。
以下のコードは、`posts_search` フィルタを使い、標準の `LIKE` 句を `MATCH` 句に動的に書き換える高等テクニックの一例だ。
/
- WP_Queryの検索をFull-Text Searchに強制転換する
- 前提:wp_posts(post_title, post_content) に FULLTEXT インデックスが貼られていること
/
add_filter(‘posts_search’, function($search, $wp_query) {
if (!is_admin() && $wp_query->is_search() && $wp_query->is_main_query()) {
global $wpdb;
$s = $wp_query->get(‘s’);
if (empty($s)) return $search;
// 検索ワードのエスケープ
$term = $wpdb->_real_escape($s);
// 標準のLIKE検索を無効化し、MATCH句を挿入
// 自然言語モードでの検索を実行
$search = $wpdb->prepare(
” AND MATCH({$wpdb->posts}.post_title, {$wpdb->posts}.post_content) AGAINST (%s IN NATURAL LANGUAGE MODE) “,
$term
);
}
return $search;
}, 10, 2);
B. 外部検索エンジンの導入(疎結合への移行)
真に大規模なトラフィック(数百万記事、秒間数千リクエスト)を捌く場合、データベースに検索を担わせてはならない。
- Elasticsearch (Luceneベース): 転置インデックスによる圧倒的な検索スピードと、日本語の形態素解析(Kuromoji等)による精度の高い検索。
- Algolia: SaaS型の検索エンジン。ホスト側のリソースを一切消費せず、フロントエンドから直接APIを叩くことで、WordPressのランタイム負荷をゼロにする。
結論:エンジニアが守るべき一線
`WP_Query` の `s` パラメータは、あくまで「プロトタイプ」や「小規模サイト」のための利便性機能に過ぎない。
我々アーキテクトの仕事は、デフォルトの設定がいつ、どのような物理的制約によって破綻するかを予見することにある。B-Treeインデックスが効かない `LIKE ‘%…%’` を放置することは、将来的な技術負債を確定させる行為に他ならない。
インフラ、データベースの内部構造、そしてアプリケーションの要件を俯瞰し、適切な「検索のオフロード」を設計すること。それが、WordPressという巨大なエコシステムを真に掌握するための、唯一の道である。