【実務・中級編】wp_optionsテーブルのautoloadデータが引き起こすMySQLクエリキャッシュの無効化とメモリ枯渇 – WordPress 内部コア・データベース構造とパフォーマンス最適化解析バイブル

wp_optionsの闇:autoload=yesが引き起こすMySQLメモリ枯渇とクエリキャッシュ無効化のメカニズム

コードレビューをしていて、未だに「とりあえず`add_option()`や`update_option()`を使えばいいや」という実装を見る。エンジニアとして、これは看過できない。

特に深く考えずに保存された大量の設定データ、サードパーティ製プラグインが吐き出す巨大なJSON、一時的なキャッシュデータが、`autoload = ‘yes’`のまま`wp_options`テーブルに放置されている現場があまりにも多すぎる。

今回は、この`autoload = ‘yes’`というWordPressの暗黙の仕様が、MySQLのメモリ構造、クエリキャッシュ(あるいはInnoDBバッファプール)、そしてPHPのプロセスにどのような致命的な負荷を与えるのか。その内部挙動をコアのソースコードレベルで解剖し、実務で使える堅牢な最適化手法を提示する。

—

1. なぜ `autoload = ‘yes’` は悪魔の仕様なのか?

WordPressの初期化プロセス(`wp-settings.php`)において、リクエストごとに必ず実行される極めて重要な関数がある。それが `wp_load_alloptions()` だ。

内部の動きを見てみよう。コアのソースコード(`wp-includes/option.php`)を覗くと、以下のロジックが走っている。

function wp_load_alloptions() {
global $wpdb;

// キャッシュがあればそれを返す(だが、マルチサイトや頻繁な更新でキャッシュパージされやすい)
$alloptions = wp_cache_get( ‘alloptions’, ‘options’ );

if ( !$alloptions ) {
// 全部の autoload = ‘yes’ のレコードを一網打尽で取得する
$suppress = $wpdb->suppress_errors();
$alloptions_db = $wpdb->get_results( “SELECT option_name, option_value FROM {$wpdb->options} WHERE autoload = ‘yes'” );
// …配列に整形してオブジェクトキャッシュへ格納
}
return $alloptions;
}

何が起きているのか?

1. すべてのリクエストで無条件に実行: 管理画面、フロントエンド、REST API、WP-CLIの実行に至るまで、WordPressは起動時に「`autoload = ‘yes’` が設定されたすべてのオプション行」を単一の巨大なクエリでメモリ上にロードする。
2. オブジェクトキャッシュの肥大化: 取得されたデータはすべてシリアライズ(または配列)され、MemcachedやRedis、あるいはPHPのメモリ(APCuなど)に載る。もしここが数MB〜数十MBに達していれば、PHPの`memory_limit`を圧迫し、アクセスが集中した瞬間にスワップアウトを引き起こす。
3. MySQL側でのペナルティ: `wp_options`の容量が大きくなりすぎると、InnoDBのバッファプール(Buffer Pool)のヒット率が下がり、ディスクI/O(あるいはOSのページキャッシュ)への依存が高まる。さらに、クエリ結果セットが大きいため、MySQLのネットワークバッファや一時メモリ(`tmp_table_size` / `max_heap_table_size`)を無駄に消費する。

「たかが設定値の読み込み」と侮ってはいけない。ここに1MBのゴミデータが溜まっているだけで、10,000PV/hのサイトであれば、毎時間10GBもの不要なデータ転送とメモリ消費をMySQLとPHP間で発生させていることになる。

—

2. 診断:自サイトの「沈黙の爆弾」を見つけ出す

まずは、現在のデータベースがどれだけ汚染されているかを定量的に把握しよう。以下のSQLをMySQLクライアントで実行し、`autoload`の総容量を確認してほしい。

SELECT
SUM(LENGTH(option_value)) AS total_size_bytes,
ROUND(SUM(LENGTH(option_value)) / 1024 / 1024, 2) AS total_size_mb,
COUNT() AS option_count
FROM wp_options
ofu WHERE autoload = ‘yes’;

さらに、上位の肥大化要因を特定するクエリも投げてみる。

SELECT
option_name,
ROUND(LENGTH(option_value) / 1024, 2) AS size_kb,
LEFT(option_value, 100) AS preview
FROM wp_options
WHERE autoload = ‘yes’
ORDER BY LENGTH(option_value) DESC
LIMIT 20;

ここで、サードパーティ製プラグインが保存したセッション情報、トランジェントの残骸、ログデータなどが上位に食い込んでいたら即座に対処が必要だ。

—

3. 堅牢な設計パターン:オプションを正しくコントロールする

プラグイン開発やテーマ開発において、データを保存する際は以下の原則を徹底しなければならない。

1. 原則 `autoload = ‘no’`: 毎回のリクエストで使わないデータ(巨大な設定配列、APIトークンのキャッシュ、ログ、一時データ)は、絶対に `autoload` を `yes` にしてはならない。
2. トランジェントAPIの活用: 有効期限付きの一時データは `wp_options` の直接操作を避け、Transient API (`set_transient()`) を使う。これらは適切に扱えば無駄な常時ロードを回避できる。
3. カスタムテーブルへの逃がす判断: 1行のサイズが数KBを超えるデータや、時系列で増え続けるデータは、`wp_options` に入れるべきではない。専用のカスタムテーブルを切るのがエンジニアリングの基本だ。

プロダクションコード例:安全なオプション管理クラス

以下に、意図せず `autoload = ‘yes’` になることを防ぎ、型安全かつ堅牢にオプションを管理するシングルトンパターンの実装例を示す。

namespace MyProject\Core;

/

  • Class OptionManager
  • wp_options への不正なアクセスと肥大化を防ぐための堅牢なラッパー

/
class OptionManager {

private static ?self $instance = null;

private function __construct() {}

public static function get_instance(): self {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}

/

  • オプションを保存する(デフォルトで autoload = ‘no’ を強制)
  • @param string $key オプション名
  • @param mixed $value 保存する値
  • @param bool $autoload 自動ロードするかどうか(基本は false 推奨)
  • @return bool

/
public function set( string $key, mixed $value, bool $autoload = false ): bool {
// データのバリデーションやサニタイズをここに挟む
$autoload_str = $autoload ? ‘yes’ : ‘no’;

// update_option は内部で存在確認と変更検知を行う
return update_option( $key, $value, $autoload_str );
}

/

  • オプションを取得する
  • @param string $key
  • @param mixed $default
  • @return mixed

/
public function get( string $key, mixed $default = false ): mixed {
$value = get_option( $key, $default );

if ( false === $value ) {
return $default;
}

return $value;
}

/

  • 大規模な設定配列の一部だけを更新・取得する設計
  • 巨大なJSONを一つのオプションにまとめる悪習を断つためのヘルパー

/
public function update_sub_option( string $parent_key, string $sub_key, mixed $value ): bool {
$data = $this->get( $parent_key, [] );
if ( ! is_array( $data ) ) {
$data = [];
}

$data[$sub_key] = $value;

// 巨大になりがちな親オプションは確実に autoload = ‘no’ で保存
return $this->set( $parent_key, $data, false );
}
}

なぜこの設計が優れているのか?

  • デフォルトの安全弁: `set()` メソッドの第3引数を `false` に倒すことで、開発者がうっかり `autoload = ‘yes’` で巨大なデータを保存するミスをコードレベルでブロックする。
  • モノリスなオプションの分解: 1つのオプションに何でもかんでも配列で詰め込み、一部が変更されるたびにシリアライズ・データベース書き込みが走る非効率性を、サブオプション管理で局所化している。

—

4. 既存の肥大化した環境をクリーンアップする手順

すでに運用中で、`autoload = ‘yes’` が荒れ果てている環境をどう立て直すか。手動でSQLを叩くのはリスクが高すぎる(特にシリアライズされたデータ構造を壊す危険があるため)。

WP-CLIを活用した安全な一括変更・削除のプラクティスを提示する。

1. 特定の不要なプレフィックスを持つオプションを一括で `autoload = ‘no’` に変更する

WP-CLIのスクリプト、またはPHPのワンライナーを走らせる。

wp eval ‘
global $wpdb;
$keys = $wpdb->get_col( “SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE “my_plugin_heavy_%” AND autoload = “yes”” );
foreach ( $keys as $key ) {
update_option( $key, get_option( $key ), “no” );
WP_CLI::line( “Updated autoload to no: {$key}” );
}
‘

2. キャッシュの完全クリア

データベース側のフラグを変更した後は、必ずWordPressのオブジェクトキャッシュおよび transients をパージする。

wp cache flush
wp transient delete –expired

—

5. テクニカルリードからの総括

WordPressのパフォーマンスチューニングと聞くと、とたんに「Varnishを入れるだの」「CDNを挟むだの」といったネットワーク層・インフラ層の話に逃げがちだ。しかし、コードの根底にあるデータベーススキーマの理解と、I/Oコストへの意識が欠けていれば、どれだけ高価なサーバーを並べてもスケールしない。

`wp_options` と `autoload` の関係性は、まさにそのエンジニアの基礎体力がモロに出るポイントだ。

「このデータを本当に毎ページ読み込む必要はあるのか?」
「この設定値は、セッションごと、あるいはリクエストごとに破棄されるべきではないか?」

コードレビューの際は、常にこの問いをチームメンバーに投げかけ、`autoload = ‘yes’` の濫用を断固として防いでいってほしい。美しいコードと正しいデータ構造こそが、真にスケーラブルなWordPressシステムを構築唯一の王道である。

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