TTFB Request Duration: サーバー処理時間の削減

Request duration(リクエスト処理時間)は、サーバーがリクエストを処理するのに費やす時間です。ほとんどのサイトにおいて最大のTTFB要因となります。Server-Timing、キャッシング、データベースの最適化、およびプラットフォームの修正について学びます。

Arjen Karel Core Web Vitals Consultant
Arjen Karel - linkedin
Last update: 2026-03-03

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) は、以下のサブパートに分類できます。

Time to First Byteを最適化したいですか? この記事では、Time to First ByteのRequest duration(リクエスト処理時間)部分に焦点を当てています。Request durationの意味がよく分からない場合は、まずTime to First Byteとは何かTTFBの問題を特定して修正するから始めてください。

Request Duration中に何が起こるのか

Request durationは、サーバーがHTTPリクエストを受信した瞬間に始まり、レスポンスの最初の1バイトをクライアントに送り返したときに終わります。その間にサーバーが行うすべての処理が、Request durationにカウントされます。一般的な動的ウェブサイトでは、これには以下が含まれます。

  1. リクエストルーティング (Request routing): ウェブサーバー (Nginx、Apache、IIS) がリクエストを受信し、それをアプリケーション層にルーティングします。
  2. アプリケーションのブートストラップ (Application bootstrap): フレームワークが初期化されます。WordPressの場合、これはコア、アクティブなプラグイン、およびテーマの読み込みを意味します。Node.js/Expressの場合、これはミドルウェアの実行を意味します。
  3. ビジネスロジック (Business logic): アプリケーションコードが実行されます(認証チェック、権限検証、データ変換、テンプレートの選択など)。
  4. データベースクエリ (Database queries): アプリケーションがMySQL、PostgreSQL、MongoDB、または使用しているデータストアにクエリを実行します。これが最も遅いステップになることがよくあります。
  5. レスポンス生成 (Response generation): アプリケーションが、テンプレート、コンポーネントツリー、またはシリアル化ロジックからHTML (またはJSON) レスポンスをレンダリングします。

これらの各ステップで時間が追加されます。十分に最適化されたサーバーであれば、一連の処理全体を100ms未満で完了させます。最適化が不十分なサーバーでは800ms以上かかることもあり、これはDNS、Connection、およびネットワーク遅延が考慮される前の段階での話です。

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のネットワークパネルにある「タイミング(Timing)」タブの下に表示され、コードに直接マッピングされるサーバー側のウォーターフォールを提供します。

PHPでのServer-Timing

PHPのhrtime(true)関数はナノ秒単位の精度を提供するため、サーバー側の個々の操作を測定するのに最適です。PHPアプリケーションにServer-Timingを追加する方法は以下の通りです。

// ナノ秒精度でデータベースのクエリ時間を測定する
$dbStart = hrtime(true);
$result = $pdo->query('SELECT * FROM products WHERE active = 1');
$dbDuration = (hrtime(true) - $dbStart) / 1e6; // Convert to ms

// テンプレートのレンダリングを測定する
$tplStart = hrtime(true);
$html = renderTemplate('product-list', ['products' => $result]);
$tplDuration = (hrtime(true) - $tplStart) / 1e6;

// ブラウザにServer-Timingヘッダーを送信する
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()を使用します。ミドルウェアパターンを使用すると、リクエストの合計処理時間と個々の操作を測定できます。

// Server-Timingヘッダー用のExpressミドルウェア
app.use((req, res, next) => {
  const start = process.hrtime.bigint();
  const timings = [];

  // レスポンスオブジェクトにヘルパーをアタッチする
  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}"`);
    };
  };

  // レスポンスを送信する前に、ヘッダーを追加する
  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();
});

// ルートハンドラーでの使用例
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に短縮しました。この商品クエリは、インデックスなしで8万行のpostmetaのフルテーブルスキャンを行っていたため、それだけで450msを占めていました。

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 8.0+はプリロード(PHP 7.4以降で利用可能)をサポートしており、サーバー起動時にフレームワークファイルをメモリにロードするため、リクエストごとに読み込む必要がなくなります。
  • XdebugやBlackfireでプロファイリングする: 最もウォールクロックタイム(実経過時間)を消費している特定の関数を特定します。

Python (Django / Flask)

Pythonアプリケーションは、Node.jsやPHPと比較してベースラインのRequest durationが高くなることがよくあります。

  • 非同期フレームワークを使用する: I/O待機がボトルネックの場合は、FastAPI、またはデータベースクエリやAPI呼び出しを並行して処理できるASGI対応のDjangoを検討してください。
  • Djangoのデータベースクエリロギングを有効にする: 設定のLOGGINGをセットしてすべてのSQLクエリをログに記録し、遅いクエリや冗長なクエリを特定します。
  • select_related()prefetch_related()を使用する: 関連するテーブルを結合するように明示的に指示しない限り、DjangoのORMは喜んでN+1クエリを生成します。

データベースのコネクションプーリング

リクエストごとに新しいデータベース接続を開くことは、Request durationが遅延する最も一般的で、かつ最も回避しやすい原因の1つです。新しい接続を行うたびに、TCPハンドシェイク、認証、セッションの初期化が必要となり、リクエストごとに5〜30msの遅延が追加される可能性があります。負荷がかかると、データベースサーバーの利用可能な接続が枯渇し、壊滅的な事態となります。

コネクションプーリングは、リクエスト間で再利用される開いた状態の接続プールを維持することで、この問題を解決します。アプリケーションはプールから接続を借り、クエリを実行し、完了したら接続を返却します。これにより、リクエストごとの接続オーバーヘッドが完全に解消されます。

ほとんどのデータベースドライバーは、ネイティブでコネクションプーリングをサポートしています。たとえば、pg (PostgreSQL) を使用するNode.jsの場合、次のように構成します。

const { Pool } = require('pg');

const pool = new Pool({
  host: 'localhost',
  database: 'myapp',
  max: 20,        // プール内の最大接続数
  idleTimeoutMillis: 30000,  // 30秒後にアイドル状態の接続を閉じる
  connectionTimeoutMillis: 2000  // 利用可能な接続がない場合は即座にフェイルさせる
});

// 新しいクライアントを作成する代わりに pool.query() を使用する
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: メモリから1秒間に何千ものリクエストを処理できる専用のHTTPキャッシュです。アプリケーションのレスポンスにCache-Controlヘッダーを構成するだけで、残りはVarnishが処理します。
  • Nginx FastCGI Cache: Webサーバーとしてすでに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改善策となります。

フルページキャッシングが有効になっているサイトの中央値のRequest durationは約35msです。キャッシングがまったくないサイトの中央値は約320msです。この差(ほぼ10倍)は、何よりもまずサーバー側キャッシングに投資すべきだという、私が主張できる最も強力な根拠です。

Request durationは、Core Web Vitalsの診断指標であるTime to First Byteの一部です。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: サービスワーカーのパフォーマンス、ブラウザキャッシュの検索、およびbfcache。
  • DNS Duration: DNSプロバイダーの選択、TTLの構成、およびdns-prefetch。
  • Connection Duration: TCPハンドシェイク、TLSの最適化、HTTP/3、およびpreconnect。

大規模サイトのCore Web Vitalsを通してきました。

欧州の大手メディアやEコマース基盤で50万ページ超。修正コードを書き、フィールドデータで効果を検証します。

進め方を見る
TTFB Request Duration: サーバー処理時間の削減Core Web Vitals TTFB Request Duration: サーバー処理時間の削減