【入門編】wp_postsテーブルのpost_content_filteredカラムの活用:検索負荷を軽減する非正規化の極意 – WordPress 内部コア・データベース構造とパフォーマンス最適化解析バイブル

こんにちは!WordPressの内部構造やパフォーマンスチューニングの世界へようこそ。

今回は、多くの開発者がスルーしがちでありながら、大規模サイトや高負荷な検索機能を実装する上で絶対に避けて通れない「データベースの非正規化と検索負荷の軽減」についてお話ししますね。

特に焦点を当てるのは、`wp_posts` テーブルの片隅にひっそりと存在する `post_content_filtered` カラムです。ここを使いこなせるようになると、WordPressの検索パフォーマンスが劇的に変わりますよ。

他の言語(Ruby on RailsやLaravelなど)からWordPressに入ってきた方だと、「えっ、データベース構造をいじっていいの?」と思われるかもしれませんが、今回はWordPressが標準で用意してくれている専用カラムを賢く使う安全な方法を解説していきます。ここをクリアすれば、WordPressのデータ構造に対する理解が一段と深まりますよ。一緒にバッチリマスターしていきましょう!

—

1. なぜ `LIKE` 検索は悪なのか?(データベースの悲鳴)

WordPressで投稿内容を検索するとき、標準では次のようなSQLが実行されます。

SELECT FROM wp_posts
WHERE post_content LIKE ‘%キーワード%’
AND post_type = ‘post’
AND post_status = ‘publish’;

この `LIKE ‘%キーワード%’` というクエリ、一見すると普通に見えますが、データベース(MySQL / MariaDB)の視点から見ると「フルテーブルスキャン(全件走査)」の刑に処される最悪の構文です。

データの保存状態と検索のイメージ

[ wp_posts テーブルのイメージ ]
+—-+——————-+—————————————————+
| ID | post_title | post_content (HTMLタグやショートコードが混ざった生データ) |
+—-+——————-+—————————————————+
| 1 | データベースの極意 |

WordPressの内部構造を徹底解説…

|
| 2 | パフォーマンス改善 |

キャッシュプラグインを使ってサイトを爆速にする方法…

|
+—-+——————-+—————————————————+

`post_content` には、HTMLタグ (`

`) やショートコード (``) がそのまま入っていますよね。
データベースはインデックス(索引)を張ることができず、すべての行のHTML文字列を左から右まで総なめでチェックします。データが10万件を超えたあたりからサイト全体が重くなり、最悪の場合はデータベースサーバーのCPU使用率が100%に張り付いてしまいます。

「じゃあ、どうすればいいの?」

そこで登場するのが、今回主役の `post_content_filtered` カラムです。

—

2. `post_content_filtered` カラムとは何か?

WordPressの `wp_posts` テーブルの構造を覗いてみると、実はあらかじめ用意されている予備のテキスト用カラムがあります。それが `post_content_filtered` です。

公式ドキュメント的な定義としては「フィルター済みの投稿コンテンツを格納するため」とされていますが、実務の現場では、「検索対象となるテキストから、HTMLタグや不要なメタ文字をすべて剥ぎ取った『純粋なテキスト』をキャッシュしておくための非正規化カラム」として使われます。

非正規化(Denormalization)の魔法

データベースの正規化は美しい設計ですが、Webのパフォーマンスにおいては「スピード」が正義になることがあります。

  • 正規化の状態(デフォルト): 毎回HTMLからテキストを抽出して検索する(遅い)
  • 非正規化の状態(今回の極意): 記事の保存時に、検索用のプレーンテキストを計算して専用カラムに保存しておく(速い)

[ 検索クエリの実行フローの比較 ]

【従来】
ユーザー検索 ──> wp_posts.post_content (HTML混じり) ──> 毎回全件走査 (重い!)

【改善後】
ユーザー検索 ──> wp_posts.post_content_filtered (純粋なテキスト) ──> インデックス活用 (爆速!)

—

3. 実装の全体像:保存時にテキストを抽出し、検索時に活用する

それでは、実際にコードを書いていきましょう。
やるべきことは大きく分けて以下の2つです。

1. 記事の保存・更新時 (`save_post`): `post_content` からHTMLを削ぎ落とし、`post_content_filtered` に書き込む。
2. 検索時 (`posts_search` やカスタムクエリ): 検索対象を `post_content` ではなく `post_content_filtered` に向ける。

ステップ1:記事保存時にプレーンテキストを生成して格納する

まずは、投稿が保存されるタイミングで、HTMLやショートコードを除去したテキストを `post_content_filtered` に自動保存するフックを記述します。

/

  • 投稿保存時に post_content から検索用テキストを生成して post_content_filtered に格納する
  • @param int $post_id 投稿ID
  • @param WP_Post $post 投稿オブジェクト

/
function my_update_content_filtered( $post_id, $post ) {
// リビジョンや自動保存、ゴミ箱行きの場合は何もしない(無駄な処理を防ぐ)
if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) || ‘publish’ !== $post->post_status ) {
return;
}

// 無限ループを防ぐため、フックを一時的に外す(remove_action)のがプロの技
remove_action( ‘save_post’, ‘my_update_content_filtered’, 10, 2 );

// 1. ショートコードを展開・除去する
$text = strip_shortcodes( $post->post_content );

// 2. HTMLタグを完全に除去する
$text = wp_strip_all_tags( $text );

// 3. 改行やタブをスペースに置換し、連続する空白を整理する
$text = preg_replace( ‘/\s+/’, ‘ ‘, $text );

// 4. wp_posts テーブルの post_content_filtered カラムを直接アップデート
global $wpdb;
$wpdb->update(
$wpdb->posts,
array( ‘post_content_filtered’ => trim( $text ) ),
array( ‘ID’ => $post_id ),
array( ‘%s’ },
array( ‘%d’ )
);

// フックを元に戻す
add_action( ‘save_post’, ‘my_update_content_filtered’, 10, 2 );
}
add_action( ‘save_post’, ‘my_update_content_filtered’, 10, 2 );

ここで陥りがちな文法エラー・注意点

  • 無限ループの罠: `save_post` フックの中でデータベースを更新 (`$wpdb->update`) すると、その更新が再び `save_post` を発火させ、サーバーがクラッシュ(メモリオーバー)します。必ず一度 `remove_action` でフックを解除してから処理を行い、最後に `add_action` で戻すという「ガード処理」を忘れないようにしましょう。

—

ステップ2:検索クエリを最適化する

次に、WordPressの標準検索が実行された際に、負荷の高い `post_content` ではなく、私たちが綺麗に整えた `post_content_filtered` を参照するようにSQLを書き換えます。

/

  • 検索対象のカラムを post_content から post_content_filtered に変更する
  • @ុparam string $search 検索SQLのWHERE句
  • @param WP_Query $wp_query クエリインスタンス
  • @return string 変更後のWHERE句

/
function my_optimize_search_column( $search, $wp_query ) {
// 管理画面やメインの検索クエリ以外では実行しない
if ( is_admin() || ! $wp_query->is_search() || ! $wp_query->is_main_query() ) {
return $search;
}

global $wpdb;

// 検索ワードを取得
$s = $wp_query->get( ‘s’ );
if ( empty( $s ) ) {
return $search;
}

// ここが肝:wp_posts.post_content を wp_posts.post_content_filtered に置換
// 標準のWordPress検索SQLの挙動に合わせて安全に書き換えます
$search = preg_replace(
“/{$wpdb->posts}\.post_content\s+LIKE/”,
“{$wpdb->posts}.post_content_filtered LIKE”,
$search
);

return $search;
}
add_filter( ‘posts_search’, ‘my_optimize_search_column’, 10, 2 );

—

4. 既存データへの移行(マイグレーション)を忘れない

新しいコードを仕込んでも、過去に書かれた既存の投稿データの `post_content_filtered` は空っぽのままです。ここを埋めるための「一括変換処理(バッチ処理)」を一度だけ実行する必要があります。

WP-CLI(コマンドラインツール)や、一度だけ読み込ませるスクリプトを書いて、すべての公開済み投稿に対して先ほどのテキスト抽出ロジックを走らせましょう。

// 例:データベースを一括更新する簡易スクリプトのイメージ
global $wpdb;
$posts = $wpdb->get_results( “SELECT ID, post_content FROM {$wpdb->posts} WHERE post_type = ‘post’ AND post_status = ‘publish'” );

foreach ( $posts as $p ) {
$text = trim( preg_replace( ‘/\s+/’, ‘ ‘, wp_strip_all_tags( strip_shortcodes( $p->post_content ) ) ) );

$wpdb->update(
$wpdb->posts,
array( ‘post_content_filtered’ => $text ),
array( ‘ID’ => $p->ID ),
array( ‘%s’ },
array( ‘%d’ )
);
}

※データ量が多い場合は、メモリ枯渇を防ぐために `LIMIT` と `OFFSET` を使って分割処理(チャンク処理)を行ってくださいね。

—

まとめ:データベースの本質を理解したエンジニアへ

今回は、`wp_posts` テーブルの隠し玉的カラムである `post_content_filtered` を使った、検索負荷軽減の非正規化テクニックを解説しました。

  • HTML混じりのデータ検索は重いので、検索専用のプレーンテキストをあらかじめ作っておく。
  • 保存時 (`save_post`) にテキストを加工して `post_content_filtered` に保存する。
  • 検索時 (`posts_search`) に参照先をスムーズに切り替える。

このアプローチは、単なるWordPressのカスタマイズに留まらず、あらゆるWebアプリケーションにおける「パフォーマンスチューニングの基本思想」そのものです。

「なぜこのカラムが存在するのか?」というデータベース設計者の意図を汲み取り、システムを最適化していく――。この感覚が身につけば、どんな巨大なWordPressサイトを任されてもビクともしない、頼もしいフルスタックエンジニアになれますよ。

ここをクリアできれば、あなたのWordPressスキルは間違いなくワンランク上のステージに到達しています。ぜひ実際の開発環境で試してみてくださいね!

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