2026年6月30日

2026年6月30日

WordPressでServer-Timing APIを使ってバックエンドの処理時間を診断する

TTFB(最初の1バイトが届くまでの時間)が遅いとき、原因がプラグインの読み込みなのか、データベースなのか、メインクエリなのかは、ブラウザ側からは見えません。Server-Timing(Server-Timing API とも呼ばれるHTTPヘッダーの仕組み)を使うと、サーバー内部の処理時間を区間ごとにレスポンスへ書き込み、Chrome の開発者ツールでそのまま確認できます。この記事では、仕組みの説明から、WordPress 用の小さな計測プラグインの作り方、結果の読み方、本番運用での注意点までを説明します。

Server-Timingの仕組み

Server-Timing は、W3C で仕様化されている HTTP レスポンスヘッダーです。サーバーが「どの処理に何ミリ秒かかったか」を、次のような形式でブラウザに伝えます。

Server-Timing: boot;dur=182.4;desc="Core+Plugins", db;dur=35.0;desc="42 queries", total;dur=310.2

1つの項目は「名前」「dur(ミリ秒)」「desc(説明)」でできていて、カンマ区切りで複数並べられます。名前にはスペースを含めず、英数字とハイフンなどの短い記号にしておくのが安全です。

ブラウザ側では、次の2か所で値を確認できます。

  • Chrome の開発者ツールの Network パネルでリクエストを選び、Timing タブを開くと、Server Timing として各項目が棒グラフで表示されます
  • JavaScript の Performance API で、performance.getEntriesByType('navigation')[0].serverTiming から読み取れます(HTTPS のページで利用でき、別オリジンのリソースは相手側の許可ヘッダーが必要です)

Server-Timing は「サーバーが自己申告した時間」です。ネットワークの往復時間や、Webサーバーが PHP にリクエストを渡すまでの待ち時間は含まれません。TTFB全体との差が大きければ、PHPの外側(サーバーの混雑、PHP-FPMの待ち行列、CDN、TLS接続など)に原因があると推測できます。

WordPressの処理のどこを区切って計測するか

WordPress の1回の表示は、大まかに次の順で進みます。

  1. コアの読み込みと、有効なプラグインの読み込み(plugins_loaded アクションまで)
  2. テーマの読み込みと初期化処理(after_setup_theme、init などを経て wp_loaded まで)
  3. リクエストの解析とメインクエリの実行、リダイレクト判定(wp() の処理と template_redirect)
  4. テンプレートの出力(ヘッダー、本文、サイドバー、フッター)

それぞれの境目となるアクションで時刻を記録すれば、「プラグインの読み込みが重い」「init にぶら下がった処理が重い」「メインクエリが重い」を切り分けられます。

注意点は、HTTPヘッダーは本文を出力する前にしか送れないことです。テンプレートの出力時間まで含めて計測するには出力バッファリングが必要になり、仕組みが複雑になります。今回は、テンプレート出力の直前(template_include フィルター)でヘッダーを送る方式にし、1〜3の区間を計測します。テンプレートの重さは、全体の体感時間との差から推測します。

計測用プラグインを作る

wp-content/mu-plugins/ に次のファイルを置きます。mu-plugins はプラグイン一覧から無効化できない代わりに、通常のプラグインより先に読み込まれるため、計測コードの置き場所に向いています。設置前にはサイトのバックアップを取り、できればステージング環境で試してください。

<?php
/**
 * Plugin Name: Simple Server-Timing
 * Description: 管理者にだけ Server-Timing ヘッダーでバックエンドの処理時間を返す。
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

$GLOBALS['simple_st_marks'] = array();

// 区切りとなるアクションの最後(最も遅い優先度)で時刻を記録する
foreach ( array( 'plugins_loaded', 'wp_loaded' ) as $simple_st_hook ) {
	add_action(
		$simple_st_hook,
		function () use ( $simple_st_hook ) {
			$GLOBALS['simple_st_marks'][ $simple_st_hook ] = microtime( true );
		},
		PHP_INT_MAX
	);
}

add_filter(
	'template_include',
	function ( $template ) {
		// ヘッダー送信済み、または管理者以外には何もしない
		if ( headers_sent() || ! current_user_can( 'manage_options' ) ) {
			return $template;
		}

		$now   = microtime( true );
		$start = isset( $_SERVER['REQUEST_TIME_FLOAT'] ) ? (float) $_SERVER['REQUEST_TIME_FLOAT'] : $now;
		$marks = $GLOBALS['simple_st_marks'];
		$items = array();

		if ( isset( $marks['plugins_loaded'] ) ) {
			$items[] = sprintf( 'boot;dur=%.1F;desc="Core+Plugins"', ( $marks['plugins_loaded'] - $start ) * 1000 );
		}
		if ( isset( $marks['plugins_loaded'], $marks['wp_loaded'] ) ) {
			$items[] = sprintf( 'init;dur=%.1F;desc="Theme+init"', ( $marks['wp_loaded'] - $marks['plugins_loaded'] ) * 1000 );
		}
		if ( isset( $marks['wp_loaded'] ) ) {
			$items[] = sprintf( 'query;dur=%.1F;desc="Request+Main query"', ( $now - $marks['wp_loaded'] ) * 1000 );
		}

		// SAVEQUERIES が有効なときだけ、DBクエリの合計時間を出す
		global $wpdb;
		if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES && ! empty( $wpdb->queries ) ) {
			$db_time = 0.0;
			foreach ( $wpdb->queries as $query ) {
				$db_time += (float) $query[1];
			}
			$items[] = sprintf( 'db;dur=%.1F;desc="%d queries"', $db_time * 1000, count( $wpdb->queries ) );
		}

		$items[] = sprintf( 'total;dur=%.1F;desc="Before template"', ( $now - $start ) * 1000 );

		header( 'Server-Timing: ' . implode( ', ', $items ) );

		return $template;
	},
	PHP_INT_MAX
);

ポイントは3つです。

  • $_SERVER['REQUEST_TIME_FLOAT'] は PHP がリクエストを受け取った時刻で、ここを起点に各区間を計算しています
  • current_user_can( 'manage_options' ) で管理者にだけヘッダーを返します。処理時間はサーバー構成を推測する材料になるため、一般の訪問者には見せない設計にします
  • sprintf の %.1F は大文字の F です。小文字の f はロケールの影響を受け、環境によって小数点がカンマになることがあるためです

データベースの時間は、wp-config.php に define( 'SAVEQUERIES', true ); を書いたときだけ出ます。SAVEQUERIES はすべてのクエリと呼び出し元を記録するため、メモリと処理時間が増えます。調査のあいだだけ有効にし、終わったら外してください。

結果を確認する

管理者としてログインした状態で、調べたいページを開きます。

  1. Chrome で開発者ツールを開き、Network パネルを表示した状態でページを再読み込みする
  2. 一覧の先頭にあるHTMLドキュメントのリクエストを選ぶ
  3. Timing タブの Server Timing の欄で、boot・init・query・db・total を確認する

コンソールで値を一覧したい場合は、次のように入力します。

performance.getEntriesByType('navigation')[0].serverTiming
  .map(t => `${t.name}: ${t.duration.toFixed(1)}ms ${t.description}`)
  .join('\n');

何も表示されない場合は、ページキャッシュが返した応答を見ている可能性があります。多くのキャッシュプラグインはログイン中のユーザーにキャッシュを返しませんが、設定によって異なります。また、サーバーやCDNの設定によってはヘッダーが取り除かれることがあります。CDNによっては、独自の Server-Timing 項目を追加するものもあるため、自分で付けた名前と区別して読んでください。

数値の読み方と次の一手

計測値は1回だけで判断せず、同じページを何度か再読み込みして傾向を見ます。初回はOPcacheやオブジェクトキャッシュが温まっていないため、遅く出やすいからです。

  • boot が大きい:プラグインの読み込み自体が重い状態です。プラグインを一時的に1つずつ無効化して差を比べると、原因を絞り込めます(本番ではなくステージングで行ってください)
  • init が大きい:init などで重い処理を毎回実行しているプラグインやテーマがあります。外部APIへの同期通信や、毎回のオプション更新が典型例です
  • query・db が大きい:メインクエリや、その前後で実行されるクエリが重い状態です。Query Monitor プラグインを併用すると、遅いクエリとその呼び出し元を特定できます
  • total は小さいのに体感が遅い:テンプレート出力やPHPの外側に原因があります。サイドバーの関連記事やウィジェットの処理、サーバーの待ち時間を疑います

より細かく計測したい場合は、気になる処理の前後で microtime( true ) を記録し、同じ方法で項目を足していけば、任意の区間を可視化できます。

公式の選択肢として、WordPress のパフォーマンスチームが開発している Performance Lab プラグインには Server-Timing の仕組みが組み込まれており、独自の計測項目を登録する関数も用意されています(2026年10月現在)。提供形態や関数の仕様はバージョンで変わる可能性があるため、利用前にプラグインの説明を確認してください。自作コードとPerformance Labを同時に使う場合は、ヘッダーが重複しないよう注意します。

まとめ

  • Server-Timing はサーバー内部の処理時間を区間ごとにブラウザへ伝えるHTTPヘッダーで、Chrome の開発者ツールで確認できる
  • WordPress では plugins_loaded、wp_loaded、template_include を境目にすると、プラグイン読み込み・初期化・メインクエリを切り分けられる
  • ヘッダーは管理者にだけ返し、SAVEQUERIES は調査中だけ有効にする
  • 数値は複数回の計測で傾向を見て、重い区間に応じて Query Monitor やプラグインの切り分けで原因を特定する

お気軽にご相談ください

お見積りへ お問い合わせへ
▲