WordPressのデータベースには、記事や固定ページとは別に、サイト全体の設定値をしまっておく wp_options というテーブルがある。サイトのタイトル、パーマリンクの形式、有効なプラグインの一覧、各プラグインの設定——こうした値はすべてこのテーブルに1行ずつ保存されている。
このテーブルには autoload という列がある。一見ただのフラグだが、ここが「はい」になっている行は、サイトのどのページが表示されるときも、毎回まとめて読み込まれる。長く運用しているサイトで管理画面の表示がじわじわ重くなったとき、原因の1つとして真っ先に疑われるのがこの autoload の肥大化だ。
前回の「データベースインデックスの基礎」に続き、今回もこのブログ自身が動いているWordPress(7.1系)のデータベースで、読み取り専用のクエリを実行して確かめた実測値を材料に、autoload の仕組みと、肥大化したときに何が起きるのかを整理する。
wp_options は「サイト全体の設定の置き場」
wp_options は、option_name(名前)と option_value(値)の組を1行ずつ持つ、シンプルなキー・バリュー型のテーブルである。このブログでは全体で215行あった。
| 列 | 内容 |
|---|---|
option_id |
連番のID |
option_name |
設定の名前(blogname、permalink_structure など) |
option_value |
設定の値(配列などはシリアライズされた文字列で入る) |
autoload |
毎回まとめて読み込むかどうか |
プラグインやテーマは、get_option() / update_option() といった関数を通してこのテーブルを読み書きする。WordPress本体の設定も、プラグインの設定も、同じテーブルに同居している点がポイントになる。
autoload は「毎回まとめて読む」という印
WordPressは1回のリクエスト(ページ表示1回分)の処理の早い段階で、wp_load_alloptions() という関数を呼び、autoload が有効な行を1本のクエリでまとめて取得してメモリ上に保持する。以後、そのリクエストの中で get_option() が呼ばれたとき、対象がこのまとめ読みに含まれていれば、データベースに問い合わせずにメモリ上の値を返す。
一方、autoload が無効な行は、get_option() で実際に必要になったときに、その都度個別のクエリで読みに行く。
| autoload 有効 | autoload 無効 | |
|---|---|---|
| 読み込むタイミング | リクエストの早い段階で全件まとめて | 必要になったときに1件ずつ |
| クエリの本数 | まとめて1本 | 使うたびに1本 |
| 使わないリクエストでの負担 | 使わなくても毎回読む | 読まない |
つまり autoload は、「ほぼ毎回使う設定をまとめて読んでおき、細かいクエリを何十本も発行しないで済ませる」ための仕組みである。サイト名やパーマリンク設定のように、どのページでも必ず参照する値には合理的な設計だ。
問題は表の最後の行にある。autoload が有効な行は、そのリクエストで使うかどうかに関係なく、毎回読み込まれる。ある管理画面でしか使わない設定や、何年も前に削除したプラグインの設定であっても、autoload が有効なまま残っていれば、トップページを表示するたびに読まれ続ける。
このブログの autoload を実測する
実際に、このブログの wp_options を autoload の値ごとに集計してみた(wp db query で SELECT のみを実行・DBへの変更はしていない)。
SELECT autoload, COUNT(*) AS cnt, SUM(LENGTH(option_value)) AS bytes
FROM wp_options GROUP BY autoload;
| autoload の値 | 行数 | 値の合計バイト数 | まとめ読みの対象か |
|---|---|---|---|
on |
101 | 38,669 | 対象 |
auto |
84 | 10,063 | 対象 |
yes |
3 | 40 | 対象 |
off |
24 | 6,022 | 対象外 |
no |
3 | 3 | 対象外 |
215行のうち188行、値の合計で約48KBがまとめ読みの対象になっている。wp eval で wp_load_alloptions() の戻り値を数えても、同じく188件だった。このブログは記事数もプラグイン数も少ない小規模なサイトなので、この程度の量なら問題にはならない。
autoload の値が yes/no だけではない理由
表を見て、yes/no 以外に on/off/auto という値が並んでいることに気付いた人もいるだろう。WordPress 6.6 で autoload の扱いが見直され、次のような値が使われるようになった。
on/off: 明示的に「まとめ読みする / しない」を指定した値auto: 登録時に明示指定がなく、WordPressの判断に任された値(現状はまとめ読みの対象として扱われる)auto-on/auto-off: WordPressが自動で判断した結果として記録される値yes/no: 6.6より前からの旧来の値。現在も有効な値として扱われ、このブログにも数件残っている
どの値がまとめ読みの対象になるかは、wp_autoload_values_to_autoload() という関数が返す一覧で決まる。このブログの環境で確認すると、yes・on・auto-on・auto の4つだった。
また6.6以降は、autoload を明示指定せずに大きな値を保存しようとした場合、シリアライズ後のサイズが150,000バイト(wp_max_autoloaded_option_size フィルターの既定値)を超えると、自動的にまとめ読みの対象から外されるようになっている。巨大な値が知らないうちに毎回読み込まれる事態を、コア側で防ぐための仕組みだ。ただし、プラグインが明示的に autoload を有効にして保存した値にはこの自動判定は働かないため、この仕組みだけで肥大化がすべて防げるわけではない。
大きい順に並べると「なぜ毎回必要か」が見える
まとめ読みの対象になっている行を、値の大きい順に並べてみた。
SELECT option_name, LENGTH(option_value) AS len, autoload
FROM wp_options
WHERE autoload IN ('yes', 'on', 'auto-on', 'auto')
ORDER BY len DESC LIMIT 10;
| option_name | バイト数 | autoload | 何のための値か |
|---|---|---|---|
_transient_wp_core_block_css_files |
22,266 | on | コアのブロック用CSSファイル一覧の一時保存 |
rewrite_rules |
8,349 | on | URLから表示内容を決めるための書き換えルール |
wp_user_roles |
3,177 | on | 権限グループ(ロール)と権限の定義 |
cron |
2,748 | on | WP-Cronの予定されたイベント一覧 |
| (プラグインの設定値 × 数件) | 数百〜2,300程度 | auto | 各プラグインの設定 |
上位のコア由来の値は、どれも「毎回必要」な理由がはっきりしている。
rewrite_rules:/blog/記事のslug/のようなURLを受け取ったとき、どの記事を表示するかを決めるルール表。どのページの表示でも最初に参照するwp_user_roles: 「このユーザーはこの操作をしてよいか」を判定する権限表。ログイン中の操作やREST APIの処理で参照されるcron: ページ表示のたびに「実行時刻を過ぎた予定がないか」を確認するための一覧(WP-Cronの仕組みは「crontab構文の基礎とWP-Cronとの違い」で解説した)
一番大きい _transient_wp_core_block_css_files は、名前が _transient_ で始まっている。これは option ではなくトランジェント(一時的なキャッシュ値)だが、データベース上では同じ wp_options テーブルに保存されている。トランジェントの仕組みについては「WordPressのトランジェント(transient)APIの仕組み」で詳しく書いた。
有効期限のないトランジェントは autoload される
トランジェントがまとめ読みに入るかどうかは、保存時に有効期限を指定したかどうかで決まる。WordPressコアの set_transient() の実装を読むと、次のような分岐になっている(該当部分を簡略化)。
$autoload = true;
if ( $expiration ) {
$autoload = false;
// 有効期限を別の行(_transient_timeout_...)に保存
}
add_option( $transient_option, $value, '', $autoload );
有効期限を指定したトランジェントは autoload が無効になり、指定しなかった(無期限の)トランジェントは autoload が有効になる。無期限のトランジェントは「次に使うときまで確実に残っていてほしい値」として扱われるためだ。
このブログの集計でも、_site_transient_update_plugins(プラグイン更新情報のキャッシュ)のような有効期限付きの値は off 側に入っていた。裏を返すと、有効期限を付けずに大きなトランジェントを保存するプラグインがあると、その値は毎回のまとめ読みに加わり続ける。
まとめ読みのクエリは「全件スキャン」で動いている
では、このまとめ読みのクエリはデータベース上でどう実行されているのか。前回の記事で使った EXPLAIN で確認してみた。
EXPLAIN SELECT option_name, option_value FROM wp_options
WHERE autoload IN ('yes', 'on', 'auto-on', 'auto');
| type | possible_keys | key | rows | Extra |
|---|---|---|---|---|
| ALL | autoload | NULL | 215 | Using where |
wp_options には autoload 列のインデックスがあり(SHOW INDEX で確認済み)、possible_keys にも候補として挙がっている。それでも実際の key は NULL、type=ALL の全件スキャンが選ばれた。
これは前回説明した「一致する行が全体の大部分を占める場合、オプティマイザはあえてフルスキャンを選ぶ」の典型例である。215行中188行が条件に一致するなら、インデックスをたどって1行ずつ読むより、テーブルを頭から読んだほうが手間が少ない。
ここから分かる大事な点は、autoload の重さは「探す手間」ではなく「読む量」で決まるということだ。インデックスの工夫で軽くできる種類の問題ではなく、まとめ読みの対象になっている行と値の量そのものを減らすしかない。
肥大化すると何が重くなるのか
autoload の対象が数MBに膨らんだサイトでは、次のような影響が出る。
1. キャッシュが効かないリクエストのすべて
ページキャッシュ(HTMLを丸ごと保存して返す仕組み)が効いている訪問者向けのページでは、PHPもデータベースも動かないため、autoload の量は影響しない。影響が出るのは、PHPが実際に動くリクエストである。
- 管理画面の操作(投稿の編集、設定画面、プラグイン一覧など)
- ログイン中のユーザーによる閲覧
- REST APIへのリクエスト
- WP-Cronの実行
- キャッシュの有効期限が切れた直後の最初の表示
管理画面だけが妙に重い、というサイトでまず autoload を疑うのはこのためだ。
2. PHPのメモリ使用量
まとめ読みした値は、そのリクエストが終わるまでPHPのメモリ上に保持される。配列としてシリアライズされた値は、読み込み時に展開されてさらにメモリを使う。autoload が数MBあるサイトでは、それだけでリクエストごとのメモリ使用量が底上げされ、メモリ上限(WP_MEMORY_LIMIT)に近い状態で動くことになる。
3. 永続オブジェクトキャッシュを使っている場合
RedisやMemcachedなどの永続オブジェクトキャッシュを導入しているサイトでは、まとめ読みの結果(alloptions)が1つの大きなキャッシュ項目として保存される。データベースへのクエリは減るが、毎回その大きな項目を丸ごと取り出して展開する負担は残る。
さらに、キャッシュの種類によっては1項目あたりのサイズに上限がある。まとめ読みの結果がその上限を超えるとキャッシュに保存できず、毎回データベースから読み直すことになる。「オブジェクトキャッシュを入れたのに管理画面が軽くならない」という相談で、autoload の肥大化が見つかることがあるのはこのためだ。
なお、このブログは永続オブジェクトキャッシュを使っていない(wp_using_ext_object_cache() で確認)ため、毎回のリクエストで上記の全件スキャンが1回実行されている。
肥大化の典型的な原因
長く運用しているサイトの autoload が膨らむ原因は、おおむね次のいずれかに当てはまる。
- 削除したプラグインの設定が残っている: プラグインを削除しても、アンインストール時の後始末処理を持たないプラグインでは、
wp_optionsに保存した設定がそのまま残る。何年も前に試しに入れて削除したプラグインの設定が、autoload 有効のまま読み込まれ続けていることは珍しくない - 無期限のトランジェントを大量に保存するコード: 前述の通り、有効期限なしのトランジェントは autoload される
- ログや統計を option に溜め続けるプラグイン: アクセス数や処理履歴を1つの option に配列で追記していく設計だと、時間とともに値が際限なく大きくなる
- 大量の設定を1つの配列に詰め込む設計: 一部の画面でしか使わない設定まで、まとめて1つの autoload 有効な option に入っている
どれも「その日に何かを壊した」わけではなく、運用の年数とともに少しずつ積み上がるタイプの問題である点が厄介だ。
Site Health が警告する目安
WordPressの管理画面「ツール → サイトヘルス」には、autoload の合計サイズを確認する項目がある。コアのコードを読むと、合計が 800,000バイト(site_status_autoloaded_options_size_limit フィルターの既定値)を超えると、「自動読み込みされたオプションがパフォーマンスに影響する可能性がある」という趣旨の警告が表示される仕組みだ。
この数字は「これを超えたら必ず問題が起きる」という境界ではなく、「一度中身を確認したほうがよい」という目安と考えるのがよい。サーバーの性能やキャッシュの構成によって、実際に影響が出はじめる量は変わる。
確認のしかた(読み取りだけで分かること)
autoload の状態は、データベースを変更しない読み取り操作だけで把握できる。
# まとめ読みの対象の合計バイト数(WP-CLI)
wp option list --autoload=on --format=total_bytes
# 特定の option の autoload の値を確認
wp option get-autoload <option_name>
大きい順に並べたい場合は、本記事で使ったような SELECT 文を wp db query で実行すればよい。ここで見るべきポイントは次の3つだ。
- 合計サイズ: Site Health の目安と比べて、どの程度の量か
- 上位の行の名前: 名前の接頭辞から、どのプラグイン・テーマ由来かを推測する
- その由来が今も使われているか: 有効なプラグインのものか、すでに削除したもののか
対処の原則 — 消す前に、読む・確かめる・残す
autoload の整理は、見た目以上に慎重さが求められる作業である。wp_options には、サイトの動作に欠かせない設定と、不要になった残骸が区別なく並んでいるからだ。保守の観点からは、次の順番を守るのが基本になる。
- 先にバックアップを取る:
wp_optionsの変更は、元に戻せる状態を作ってから行う(バックアップの考え方は「3-2-1バックアップルールとは何か」を参照) - 由来を特定する: 名前だけで判断せず、どのプラグイン・テーマが使っている値かを確かめる。有効なプラグインの設定は消さない
- 削除より先に autoload を外すことを検討する: 使われている可能性が少しでもある値は、削除せずに
wp option set-autoload <option_name> offでまとめ読みの対象から外すだけにとどめる。この場合、値は残り、必要になったときに個別に読まれるだけなので、動作が壊れるリスクを抑えられる - 有効期限切れのトランジェントは専用の手段で消す: トランジェントは
wp transient delete --expiredのような専用コマンドで扱う - 変更後に確認する: 管理画面と主要なページが正常に表示されるか、エラーログに変化がないかを確かめる
なお、wp db optimize はテーブルの断片化を整理するコマンドで、autoload の量そのものは減らない。この違いは「wp db check / wp db optimize — 見落とされがちなDB健全性コマンド」でも触れた。
よくある落とし穴
- 名前が見慣れないという理由だけで option を削除する: 有効なプラグインやテーマが使っている設定だった場合、動作が壊れる。由来を確かめてから判断する
- autoload を外せば必ず軽くなると考える: 毎回使われている値の autoload を外すと、その都度個別のクエリが発行され、かえってクエリ本数が増えることがある。外す対象は「めったに使われない大きな値」に絞る
- インデックスを足せば解決すると考える: まとめ読みは一致行が大部分を占めるため全件スキャンになりやすく、重さの正体は「読む量」である
- ページキャッシュが効いている表側だけで判断する: 訪問者向けのページが軽くても、管理画面・REST API・WP-Cronには影響が出ている場合がある
- 一度整理して終わりにする: autoload の肥大化は年単位で少しずつ積み上がる。定期的に合計サイズと上位の行を確認する習慣を持つ
- プラグインを削除すれば設定も消えると思い込む: 後始末処理を持たないプラグインでは、削除後も設定が残る
まとめ
autoload は、「ほぼ毎回使う設定をまとめて1回で読んでおく」ための合理的な仕組みである。その裏返しとして、autoload が有効な行は、使うかどうかに関係なく毎回読み込まれる。このブログの実測では、215行中188行・約48KBがまとめ読みの対象で、そのクエリは全件スキャンで実行されていた。重さを決めるのは探し方ではなく、読む量そのものだ。
長く運用しているサイトほど、削除したプラグインの設定や無期限のトランジェントが少しずつ積み上がる。まずは読み取りだけで合計サイズと上位の行を確認し、手を入れる場合はバックアップ → 由来の特定 → 削除より autoload の解除 → 動作確認、の順で進めるのが安全だ。次回は、同じく wp_posts テーブルに少しずつ積み上がっていく「リビジョン・自動下書き」を取り上げる予定である。