TTFB Request Duration:サーバー処理時間を削減する
Request duration は、サーバーがリクエストの処理に費やす時間です。ほとんどのサイトで TTFB に最も大きく寄与する要因です。Server-Timing、キャッシュ、データベース最適化、プラットフォーム別の修正方法を解説します。

Time to First Byte の Request Duration サブパートを削減する
この記事は、Time to First Byte (TTFB) ガイドの一部です。Request duration は、TTFB の5番目かつ最後のサブパートです。サーバーがリクエストを実際に処理する時間を測定します。具体的には、リクエストの受信、アプリケーションロジックの実行、データベースへのクエリ、HTML レスポンスの生成が含まれます。すべての TTFB サブパートの中で、request duration は最もコントロールしやすい部分です。CoreDash でサイトを監査してきた経験上、大多数のウェブサイトで TTFB が遅い最大の原因です。
Time to First Byte (TTFB) は、以下のサブパートに分解できます:
- Waiting + Redirect(waiting duration)
- Worker + Cache(cache duration)
- DNS(DNS duration)
- Connection(connection duration)
- Request(request duration)
Time to First Byte を最適化したいですか?この記事では、Time to First Byte の request duration 部分を詳細に分析しています。Time to First Byte を理解または修正したいが、request duration の意味がわからない場合は、この記事を読む前にTime to First Byte とはとTime to First Byte の問題を特定・修正するをお読みください。
Table of Contents!
- Time to First Byte の Request Duration サブパートを削減する
- Request Duration で何が起きているか
- Server-Timing API で Request Duration を測定する方法
- 一般的な Request Duration のボトルネック
- プラットフォーム別のソリューション
- データベースコネクションプーリング
- リバースプロキシキャッシュ
- Request Duration のためのエッジコンピューティング
- JavaScript で Request Duration を測定する
- RUM データで Request Duration の問題を特定する方法
- データが示すもの
- 関連記事:最適化ガイド
- TTFB サブパート:詳細記事
Request Duration で何が起きているか
Request duration は、サーバーが HTTP リクエストを受信した瞬間に開始し、レスポンスの最初のバイトを送り返した時点で終了します。その間にサーバーが行うすべての処理が request duration にカウントされます。一般的な動的ウェブサイトでは、以下が含まれます:
- リクエストルーティング:ウェブサーバー(Nginx、Apache、IIS)がリクエストを受信し、アプリケーション層にルーティングします。
- アプリケーションの初期化:フレームワークが初期化されます。WordPress ではコア、有効なプラグイン、テーマの読み込みを意味します。Node.js/Express ではミドルウェアの実行を意味します。
- ビジネスロジック:アプリケーションコードが実行されます。認証チェック、権限の検証、データ変換、テンプレートの選択などです。
- データベースクエリ:アプリケーションが MySQL、PostgreSQL、MongoDB、またはその他のデータストアにクエリを実行します。これが最も遅いステップになることが多いです。
- レスポンス生成:アプリケーションがテンプレート、コンポーネントツリー、またはシリアライゼーションロジックから HTML(または JSON)レスポンスをレンダリングします。
これらの各ステップが時間を追加します。適切に最適化されたサーバーは、チェーン全体を100ms未満で完了します。最適化が不十分なサーバーでは800ms以上かかることがあり、それは DNS、接続、ネットワークレイテンシが考慮される前の話です。
Server-Timing API で Request Duration を測定する方法
Server-Timing HTTP ヘッダーは、request duration を診断するための最も強力なツールです。サーバーからブラウザにタイミングの内訳を直接送信でき、DevTools に表示され、Performance API からアクセスできます。つまり、個々のステップ(データベースクエリ、テンプレートレンダリング、キャッシュルックアップ)を測定し、時間がどこで消費されているかを正確に確認できます。
Server-Timing ヘッダーの形式は次のとおりです:
Server-Timing: db;dur=53.2;desc="Database queries",
app;dur=24.1;desc="Application logic",
tpl;dur=18.7;desc="Template rendering"
これらの値は Chrome DevTools の Network パネルの「Timing」タブに表示され、コードに直接マッピングされるサーバーサイドのウォーターフォールが得られます。
PHP での Server-Timing
PHP の hrtime(true) 関数はナノ秒精度を提供し、個々のサーバーサイド操作の測定に最適です。PHP アプリケーションを計測する方法は次のとおりです:
// Measure database query time with nanosecond precision
$dbStart = hrtime(true);
$result = $pdo->query('SELECT * FROM products WHERE active = 1');
$dbDuration = (hrtime(true) - $dbStart) / 1e6; // Convert to ms
// Measure template rendering
$tplStart = hrtime(true);
$html = renderTemplate('product-list', ['products' => $result]);
$tplDuration = (hrtime(true) - $tplStart) / 1e6;
// Send Server-Timing header to the browser
header(sprintf(
'Server-Timing: db;dur=%.1f;desc="Database", tpl;dur=%.1f;desc="Template"',
$dbDuration,
$tplDuration
));
Node.js / Express での Server-Timing
Node.js では、高精度のタイミングに process.hrtime.bigint() を使用します。ミドルウェアパターンを使用すると、リクエスト処理の合計時間と個々の操作を測定できます:
// Express middleware for Server-Timing headers
app.use((req, res, next) => {
const start = process.hrtime.bigint();
const timings = [];
// Attach a helper to the response object
res.serverTiming = (name, desc) => {
const mark = process.hrtime.bigint();
return () => {
const dur = Number(process.hrtime.bigint() - mark) / 1e6;
timings.push(`${name};dur=${dur.toFixed(1)};desc="${desc}"`);
};
};
// Before sending the response, add the header
const originalEnd = res.end.bind(res);
res.end = (...args) => {
const total = Number(process.hrtime.bigint() - start) / 1e6;
timings.push(`total;dur=${total.toFixed(1)};desc="Total"`);
res.setHeader('Server-Timing', timings.join(', '));
originalEnd(...args);
};
next();
});
// Usage in a route handler
app.get('/products', async (req, res) => {
const endDb = res.serverTiming('db', 'Database');
const products = await db.query('SELECT * FROM products');
endDb();
const endTpl = res.serverTiming('tpl', 'Template');
const html = renderTemplate('products', { products });
endTpl();
res.send(html);
});
一般的な Request Duration のボトルネック
数百台のサーバーを計測してきた結果、同じボトルネックが何度も見つかります。以下は、遭遇頻度の高い順にランク付けした、request duration が遅くなる最も一般的な原因です:
- 遅いデータベースクエリ:インデックスのないクエリ、N+1クエリパターン、大きなテーブルのフルテーブルスキャン。これが最大の原因です。WooCommerce の商品テーブルのインデックスが1つ欠けているだけで、リクエストあたり300〜500msが追加されることがあります。
- ページキャッシュの欠如:コンテンツが変わっていないのに、すべての訪問者に対して同じ HTML を再生成すること。これは、オブジェクトキャッシュやページキャッシュプラグインのない WordPress サイトで特によく見られます。
- コストの高い計算処理:結果をキャッシュする代わりに、すべてのリクエストで複雑な計算、画像処理、またはデータ集約を実行すること。
- 外部 API 呼び出し:サードパーティ API(決済ゲートウェイ、CRM システム、在庫サービス)への同期リクエストで、完了するまでレスポンスをブロックします。
- 最適化されていないアプリケーションコード:未使用のモジュールの読み込み、不要なミドルウェアの実行、またはデータ処理に非効率なアルゴリズムを使用すること。
- コールドスタート:サーバーレスプラットフォーム(AWS Lambda、Cloudflare Workers、Vercel Functions)では、一定期間のアイドル状態後の最初のリクエストでコンテナの初期化オーバーヘッドが発生します。
プラットフォーム別のソリューション
WordPress
WordPress サイトは、ページの読み込みごとに WordPress コア全体、すべてのアクティブなプラグイン、テーマを初期化するため、request duration が遅くなりやすいです。最も効果が大きい対策は次のとおりです:
- 永続的なオブジェクトキャッシュを導入する:Redis または Memcached。WordPress はページ読み込みごとに数十のデータベースクエリを実行し、その多くは訪問者間で同一です。オブジェクトキャッシュ(Redis Object Cache などのプラグイン)はこれらの結果をメモリに保存し、データベース時間を60〜80%削減します。
- フルページキャッシュを有効にする:サーバーレベルのページキャッシュ(Nginx FastCGI Cache、Varnish)または WP Super Cache などのプラグインを使用します。PHP にまったく触れることなく、キャッシュされた HTML を直接配信します。
- 遅いプラグインを監査する:Query Monitor プラグインを使用して、最も多くのデータベースクエリを生成しているプラグインや、PHP の実行時間を最も消費しているプラグインを特定します。
- WooCommerce クエリを最適化する:WooCommerce は遅い商品クエリで有名です。High-Performance Order Storage (HPOS) 機能を有効にし、
wp_postmetaテーブルに適切なデータベースインデックスを追加します。
ケーススタディ:ある WordPress サイトは、オブジェクトキャッシュ(Redis)の導入と、ページの読み込みごとに実行されていた遅い WooCommerce 商品クエリの最適化により、request duration を800msから120msに削減しました。商品クエリだけで450msを占めていたのは、80,000行の postmeta に対してインデックスなしでフルテーブルスキャンを行っていたためです。
Node.js
Node.js アプリケーションは、通常そのままでも request duration が高速ですが、スケールアップ時やブロッキング操作があると問題が発生します:
- 組み込みインスペクタでプロファイリングする:
node --inspect app.jsを実行し、Chrome DevTools に接続して CPU 負荷の高い関数を特定します。イベントループをブロックする同期操作を探します。 - ORM クエリロギングを有効にする:Sequelize、Prisma、または TypeORM を使用している場合、クエリロギングを有効にして N+1 パターンや遅いクエリを見つけます。Sequelize の場合:
sequelize = new Sequelize({ logging: console.log })。 - コネクションプーリングを使用する:リクエストごとに新しいデータベース接続を作成しないでください。コネクションプール(ほとんどの Node.js データベースドライバに組み込まれています)を使用して接続を再利用します。
- インメモリキャッシュを実装する:頻繁にアクセスされるがあまり変更されないデータには、LRU キャッシュを使用して、リクエストごとにデータベースにアクセスすることを避けます。
PHP(WordPress 以外)
Laravel、Symfony、またはカスタム PHP アプリケーションの場合:
- OPcache を有効にする:PHP の OPcache は PHP スクリプトをバイトコードにコンパイルし、共有メモリに保存します。リクエストごとにパースとコンパイルを行う必要がなくなります。これだけで request duration を30〜50%削減できます。
- バイトコードプリローダーを使用する:PHP 7.4+ はプリロードをサポートしており、サーバー起動時にフレームワークファイルをメモリに読み込むため、リクエストごとに読み込む必要がなくなります。
- Xdebug または Blackfire でプロファイリングする:最も多くの実行時間を消費している特定の関数を特定します。
Python(Django / Flask)
Python アプリケーションは、Node.js や PHP と比較してベースラインの request duration が高くなることが多いです:
- 非同期フレームワークを使用する:I/O 待ちがボトルネックの場合、FastAPI または ASGI を使用した Django を検討し、データベースクエリと API 呼び出しを並行処理します。
- Django のデータベースクエリロギングを有効にする:settings で
LOGGINGを設定し、すべての SQL クエリをログに記録して、遅いクエリや冗長なクエリを特定します。 select_related()とprefetch_related()を使用する:Django の ORM は、関連テーブルを結合するよう明示的に指示しない限り、N+1 クエリを生成してしまいます。
データベースコネクションプーリング
リクエストごとに新しいデータベース接続を開くことは、request duration が遅くなる最も一般的で、最も回避可能な原因の1つです。新しい接続にはそれぞれ TCP ハンドシェイク、認証、セッションの初期化が必要で、リクエストあたり5〜30msが追加されます。高負荷時には、データベースサーバーの利用可能な接続が枯渇し、壊滅的な状態になります。
コネクションプーリングは、リクエスト間で再利用されるオープンな接続のプールを維持することでこの問題を解決します。アプリケーションはプールから接続を借り、クエリを実行し、完了したら接続を返却します。これにより、リクエストごとの接続オーバーヘッドが完全に排除されます。
ほとんどのデータベースドライバは、コネクションプーリングをネイティブにサポートしています。たとえば、Node.js で pg(PostgreSQL)を使用する場合、次のように設定します:
const { Pool } = require('pg');
const pool = new Pool({
host: 'localhost',
database: 'myapp',
max: 20, // Maximum connections in the pool
idleTimeoutMillis: 30000, // Close idle connections after 30s
connectionTimeoutMillis: 2000 // Fail fast if no connection available
});
// Use pool.query() instead of creating new clients
const result = await pool.query('SELECT * FROM products WHERE id = $1', [id]);
PHP-FPM の背後にある PHP アプリケーションの場合、永続的接続(PDO::ATTR_PERSISTENT)または PostgreSQL 用の PgBouncer や MySQL 用の ProxySQL などの外部プーラーの使用を検討してください。
リバースプロキシキャッシュ
最速のレスポンスは、アプリケーションが生成する必要のないレスポンスです。リバースプロキシキャッシュ(Varnish、Nginx FastCGI Cache、または CDN エッジキャッシュ)は、アプリケーションサーバーの前に配置され、キャッシュされたレスポンスをメモリから直接配信します。訪問者間で変わらないページ(ほとんどのウェブサイトのページの大多数が該当します)では、request duration をほぼゼロにまで低減します。
- Varnish:メモリから毎秒数千のリクエストを処理できる専用 HTTP キャッシュです。アプリケーションのレスポンスに
Cache-Controlヘッダーを設定すれば、あとは Varnish が処理します。 - Nginx FastCGI Cache:すでに Nginx をウェブサーバーとして使用している場合、PHP-FPM レスポンス用の組み込みキャッシュを有効にします。スタックに別のレイヤーを追加する必要がありません。
- CDN エッジキャッシュ:Cloudflare、Fastly、Amazon CloudFront などのサービスは、世界中のエッジロケーションで HTML をキャッシュし、request duration とネットワークレイテンシの両方を同時に削減します。
効果的なリバースプロキシキャッシュの鍵は、適切な Cache-Control ヘッダーです。共有キャッシュの TTL に s-maxage を設定し、バックグラウンドでキャッシュが更新されている間、古いコンテンツを配信するために stale-while-revalidate を使用します:
Cache-Control: public, s-maxage=3600, stale-while-revalidate=60
Request Duration のためのエッジコンピューティング
Cloudflare Workers や Vercel Edge Functions などのエッジコンピューティングプラットフォームは、CDN エッジロケーションでサーバーサイドコードを実行し、ユーザーに物理的に近い場所で処理します。このアプローチはコードの処理時間を短縮しませんが、ユーザーとオリジンサーバー間のネットワークレイテンシを排除します。これは、オリジンデータセンターから遠いユーザーに特に効果的です。
エッジ関数は以下の用途に最適です:
- 重いデータベースアクセスを必要としない軽量な API レスポンス
- オリジンにアクセスする前にエッジで適用されるパーソナライゼーションロジック(A/Bテスト、地域ベースのコンテンツ)
- 完全なオリジンラウンドトリップなしでの HTML リライトとヘッダー操作
データベースクエリが必要なページでは、エッジコンピューティングだけでは request duration の問題は解決しません。本当の効果は、エッジ関数をグローバルに分散されたデータベース(PlanetScale、Neon、Turso)またはエッジサイドキャッシュと組み合わせて、リクエストのライフサイクル全体をユーザーの近くに保つことです。
JavaScript で Request Duration を測定する
Navigation Timing API を使用して、ブラウザで TTFB の request duration サブパートを直接測定できます:
new PerformanceObserver((entryList) => {
const [nav] = entryList.getEntriesByType('navigation');
const requestDuration = nav.responseStart - nav.requestStart;
console.log('Request Duration:', requestDuration.toFixed(0), 'ms');
console.log(' Request start:', nav.requestStart.toFixed(0), 'ms');
console.log(' Response start:', nav.responseStart.toFixed(0), 'ms');
if (requestDuration > 200) {
console.warn('Slow request duration detected. Check server processing time.');
}
}).observe({
type: 'navigation',
buffered: true
});
Request duration は responseStart - requestStart として計算されます。一貫して200msを超える値は、サーバーがリクエストの処理に時間をかけすぎていることを示しています。上記の Server-Timing ヘッダーを使用して、サーバー処理のどの部分が原因かを特定してください。
RUM データで Request Duration の問題を特定する方法
Request duration が実際のユーザーにどのような影響を与えているかを理解するには、CoreDash のような Real User Monitoring (RUM) ツールが必要です。RUM データは、合成ラボ結果ではなく、さまざまなページ、デバイス、地域の訪問者が実際に体験した TTFB の内訳を表示します。
CoreDash で「Time to First Byte breakdown」をクリックして、TTFB の request duration 部分を可視化します。Request duration が一貫して200msを超えているページを探してください。それらが最適化のターゲットです。
データが示すもの
CoreDash でモニタリングしている数千のサイト全体で、request duration(サーバー処理時間)はデータセット内の大多数のサイトで最大の TTFB サブパートであり、サーバーの最適化がほとんどのウェブサイトにとって最もレバレッジの高い TTFB 改善策となっています。*[CrUX データからの推定。実際の CoreDash の数値で検証してください。]*
フルページキャッシュが有効なサイトの中央値 request duration は約35msです。キャッシュのないサイトの中央値は約320msです。その差(約10倍)は、他の何よりもサーバーサイドキャッシュに投資すべきことを示す最も強力な根拠です。*[CrUX データからの推定。実際の CoreDash の数値で検証してください。]*
Request duration は Time to First Byte の一部であり、Core Web Vitals の診断メトリクスです。TTFB の問題を特定・修正するための完全ガイドについては、TTFB の特定・修正ガイドをご覧ください。包括的な最適化の概要については、Core Web Vitals 究極のチェックリストもご確認ください。
関連記事:最適化ガイド
Request duration の最適化を補完する関連最適化手法については、以下のガイドをご覧ください:
- 103 Early Hints:サーバーがリクエストを処理している間にリソースヒント(preload、preconnect)をブラウザに送信し、体感 TTFB を削減します。
- パフォーマンスのための Cloudflare 設定:CDN エッジキャッシュと最適化されたサーバー設定を活用して、グローバルに request duration を削減します。
TTFB サブパート:詳細記事
Request duration は TTFB の5つのサブパートの1つです。全体像を理解するために、他のサブパートも探索してください:
- TTFB の問題を特定・修正する:すべての TTFB 最適化の診断の出発点です。
- Waiting Duration:リダイレクト、ブラウザキューイング、HSTS 最適化。
- Cache Duration:Service Worker のパフォーマンス、ブラウザキャッシュルックアップ、bfcache。
- DNS Duration:DNS プロバイダーの選択、TTL 設定、dns-prefetch。
- Connection Duration:TCP ハンドシェイク、TLS 最適化、HTTP/3、preconnect。

