WordPressを長く触っていると、必ず一度はぶつかる壁があります。そう、「カスタムフィールド(`wp_postmeta`)の検索が遅すぎる」という問題です。
投稿数が数万件を超えたあたりで、`WP_Query`の`meta_query`を使って検索をかけると、サイトが悲鳴を上げ始めますよね。今日は、なぜそれが起こるのか、そして世界最高峰のエンジニアたちが現場でどう解決しているのか、その「禁断のテクニック」を伝授しましょう。
—
なぜ `wp_postmeta` は検索に不向きなのか?
まず、敵を知ることから始めましょう。`wp_postmeta` は「EAV(Entity-Attribute-Value)モデル」という構造をしています。
| meta_id | post_id | meta_key | meta_value |
| :— | :— | :— | :— |
| 1 | 100 | price | 5000 |
| 2 | 100 | color | red |
見ての通り、縦に長い設計なんです。1つの投稿が5つのカスタムフィールドを持っていたら、5行分スキャンしなければなりません。これに複雑な `JOIN` が絡むと、MySQLは地獄のような計算量を強いられます。
これを解決する唯一の正攻法。それが「検索用フラットテーブルの構築」です。
—
ステップ1:検索専用テーブルを定義する
WordPressの流儀に従い、`wp_` プレフィックスを考慮したテーブルを作ります。重要なのは、検索対象にするカラムにインデックスを貼ることです。
/
- 検索用テーブルの作成(プラグイン有効化時などに実行)
/
function create_search_optimized_table() {
global $wpdb;
$table_name = $wpdb->prefix . ‘search_index’;
$charset_collate = $wpdb->get_charset_collate();
$sql = “CREATE TABLE $table_name (
post_id bigint(20) NOT NULL,
price int(11) DEFAULT 0,
color varchar(50),
PRIMARY KEY (post_id),
INDEX (price),
INDEX (color)
) $charset_collate;”;
require_once(ABSPATH . ‘wp-admin/includes/upgrade.php’);
dbDelta($sql);
}
ポイントは `INDEX` です。`post_id` で検索するのではなく、`price` や `color` で絞り込めるようにインデックスを貼ることで、検索速度は劇的に変わります。
—
ステップ2:同期戦略(Hooksの活用)
データが更新されたら、自動でこのテーブルも更新しなければなりません。ここで `save_post` フックを使います。
add_action(‘save_post’, ‘sync_search_table’, 10, 2);
function sync_search_table($post_id, $post) {
global $wpdb;
// 自動保存やリビジョンは無視する鉄則
if (defined(‘DOING_AUTOSAVE’) && DOING_AUTOSAVE) return;
if ($post->post_type !== ‘product’) return; // 特定の投稿タイプのみ
$price = get_post_meta($post_id, ‘price’, true);
$color = get_post_meta($post_id, ‘color’, true);
$wpdb->replace(
$wpdb->prefix . ‘search_index’,
[
‘post_id’ => $post_id,
‘price’ => (int)$price,
‘color’ => $color
],
[‘%d’, ‘%d’, ‘%s’]
);
}
ここで `wpdb::replace` を使っているのがミソです。`INSERT` だと重複エラーになりますが、`REPLACE` なら「存在すれば更新、なければ挿入」という挙動を一行でやってくれます。
—
ステップ3:検索クエリを書き換える
最後は、`WP_Query` の代わりに、このテーブルを直接叩くSQLを発行します。
/
- 高速検索の実行
/
function get_optimized_search_results($min_price, $color) {
global $wpdb;
$query = $wpdb->prepare(”
SELECT post_id
FROM {$wpdb->prefix}search_index
WHERE price >= %d AND color = %s
“, $min_price, $color);
return $wpdb->get_col($query); // IDの配列が返る
}
// 取得したIDを WP_Query に渡せば、あとは通常通りに扱えます
$ids = get_optimized_search_results(3000, ‘red’);
$query = new WP_Query([‘post__in’ => $ids]);
—
陥りやすい罠とアドバイス
1. 「同期のタイムラグ」を考慮する:
`save_post` は管理画面からの保存には強いですが、WP-CLIでの一括更新やインポート時には動かない場合があります。初期構築時は `wp_insert_post` などのフックも網羅的に検討してください。
2. `WP_Query` の `post__in` の注意点:
`post__in` に大量のID(数千件以上)を渡すと、今度は `IN (…)` 句のパースコストで遅くなります。その場合は、ページネーション用のSQLを自作して `post_objects_ids` を取得するのが、さらなる高みへの道です。
3. インデックスの貼りすぎ:
インデックスは読み込みを速くしますが、書き込み(更新)を重くします。「本当に検索で使うカラムだけ」に絞るのが、パフォーマンスチューニングの鉄則です。
—
最後に
WordPressを「ただのブログツール」だと思っているうちは、この壁は越えられません。しかし、データベース構造を掌握し、データの流れをフックで制御できるようになったとき、WordPressは「無限の拡張性を持つWebアプリケーションフレームワーク」に姿を変えます。
ここをクリアしたあなたは、もう中級者の入り口に立っています。次は、このキャッシュを `Redis` に載せる方法を研究してみるのも面白いですよ。
応援しています。またいつでも聞きに来てくださいね。