【実務・中級編】実務中級者向け:大規模サイトにおけるページネーションのパフォーマンス最適化 – WordPress 内部コア・データベース構造とパフォーマンス最適化解析バイブル

WordPressの「ページネーション」という名の地雷原:SQL_CALC_FOUND_ROWSを排除し、0.1秒の世界へ到達せよ

WordPressの `WP_Query` を使っていて、データ量が数万件を超えた瞬間に管理画面やフロントエンドが悲鳴を上げたことはないか? その原因の多くは、WordPressがデフォルトで行う「全件カウント」にある。

今回は、大規模サイトのエンジニアなら避けては通れない `SQL_CALC_FOUND_ROWS` によるパフォーマンス劣化と、それを完全に排除した設計パターンについて深掘りする。

—

なぜデフォルトのページネーションは「罪」なのか

`WP_Query` を実行すると、WordPressは内部で以下のようなSQLを発行する。

SELECT SQL_CALC_FOUND_ROWS wp_posts. FROM wp_posts … LIMIT 0, 10;
SELECT FOUND_ROWS();

ここで重要なのは `SQL_CALC_FOUND_ROWS` だ。これは「LIMIT句を無視して、条件に合致する全行数をカウントする」という命令だが、これが大規模テーブルでは命取りになる。MySQLは全件スキャンを行い、インデックスが効いていてもフルテーブルスキャンに近い負荷が発生する。

特にカスタムタクソノミーやメタデータで複雑なJOINが発生している場合、このカウント処理だけで数秒を浪費する。ページングに必要なのは「正確な全件数」ではなく「次のページがあるかどうか」だけであるケースがほとんどだ。

—

解決策:NO_FOUND_ROWSによる最適化

この問題を解決する唯一の正攻法は、`no_found_rows` フラグを `true` にすることだ。これにより、WordPressは全件カウントを放棄する。

実装パターン:高速ページネーション設計

全件取得を諦める代わりに、「現在地+次の1件」を取得してページネーションを制御するパターンを推奨する。

/

  • 高速ページネーションを実現するためのカスタムクエリラッパー
  • @param int $paged 現在のページ
  • @param int $posts_per_page 1ページあたりの表示数
  • @return array {posts: WP_Post[], has_more: bool}

/
function get_optimized_posts(int $paged = 1, int $posts_per_page = 10): array {
// 1件多めに取得して、次のページがあるか判定する
$query = new WP_Query([
‘posts_per_page’ => $posts_per_page + 1,
‘paged’ => $paged,
‘no_found_rows’ => true, // ★ここで全件カウントを無効化
‘update_post_meta_cache’ => false, // メタデータが不要ならオフにしてさらなる高速化
‘update_post_term_cache’ => false,
]);

$posts = $query->posts;
$has_more = count($posts) > $posts_per_page;

// 余分に取得した1件を削除
if ($has_more) {
array_pop($posts);
}

return [
‘posts’ => $posts,
‘has_more’ => $has_more,
];
}

なぜこのコードが「美しい」のか

1. コストの最小化: `SQL_CALC_FOUND_ROWS` を発行しないため、MySQLの実行計画が単純になり、クエリ速度が劇的に向上する。
2. メモリの節約: `update_post_meta_cache` を明示的に `false` にすることで、不要なクエリ発行とメモリ消費を抑えている(大規模運用では必須)。
3. ロジックの堅牢性: 「全ページ数」という不正確な情報に頼らず、「次があるか」という事実ベースでUIを制御するため、大規模データでもページングが破綻しない。

—

さらに深く:インデックスの最適化戦略

もし上記の修正を行ってもクエリが遅いなら、それはデータベースのインデックス構成がWordPressの `WP_Query` に最適化されていない可能性が高い。

特にカスタムフィールド(`meta_key`, `meta_value`)でのソートは地雷だ。
`wp_postmeta` テーブルはEAV(Entity-Attribute-Value)モデルのため、大規模サイトではインデックスが効きにくい。

実務のアドバイス

  • 複合インデックスの検討: もし特定のタクソノミーと日付で頻繁にフィルタリングするなら、`wp_posts` テーブルに複合インデックスを貼ることを検討せよ。
  • クエリのオフロード: どうしても複雑な検索が必要な場合は、WordPressのコアDBに頼らず、Elasticsearch (ElasticPress) にインデックスを投げる設計に移行すべきだ。

—

エンジニアへの戒め

「WordPressは遅い」と嘆くエンジニアの9割は、`WP_Query` の引数を何も考えずにデフォルトのまま放置している。

プロダクションコードを書く際、以下の問いを自分に投げかけてほしい。
「このクエリは、本当に全件数を知る必要があるのか?」

答えがNoであれば、`no_found_rows` を入れろ。それが大規模サイトを支えるエンジニアの最低限の作法だ。コードは常に、データベースの負荷を最小化する方向へ向かわせろ。それが、あなたの書くシステムを「伝説」に変える唯一の道だ。

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