Navigation Timing と Resource Timing を使用して実際の読み込みのパフォーマンスを評価する方法

Navigation API と Resource Timing API を使用して、現場での読み込みパフォーマンスを評価する方法の基本を学びます。

公開日: 2021 年 10 月 8 日

ブラウザのデベロッパー ツールの [Network] パネル(または Chrome の Lighthouse)で接続スロットリングを使用して読み込みパフォーマンスを評価したことがある方は、パフォーマンス チューニングにこれらのツールがどれほど便利であるかをご存じでしょう。一貫した安定したベースライン接続速度で、パフォーマンスの最適化の影響をすばやく測定できます。唯一の問題は、これは合成テストであるため、フィールドデータではなくラボデータが得られることです。

合成テストは本来悪いものではありませんが、実際のユーザーがウェブサイトを読み込む速度を表すものではありません。そのためには、Navigation Timing API と Resource Timing API から収集できるフィールド データが必要です。

現場での読み込みパフォーマンスを評価する API

Navigation Timing と Resource Timing は 2 つの類似した API であり、2 つの異なるものを測定するかなり重複があります。

  • Navigation Timing は、HTML ドキュメントのリクエスト速度(ナビゲーション リクエスト)を測定します。
  • Resource Timing は、CSS、JavaScript、画像、その他のリソースタイプなど、ドキュメントに依存するリソースに対するリクエストの速度を測定します。

これらの API は、ブラウザで JavaScript を使用してアクセスできるパフォーマンス エントリ バッファにデータを公開します。パフォーマンス バッファにクエリを実行する方法は複数ありますが、一般的な方法は performance.getEntriesByType を使用する方法です。

// Get Navigation Timing entries:
performance.getEntriesByType('navigation');

// Get Resource Timing entries:
performance.getEntriesByType('resource');

performance.getEntriesByType は、パフォーマンス エントリ バッファから取得するエントリのタイプを記述する文字列を受け入れます。'navigation''resource' は、それぞれ Navigation Timing API と Resource Timing API のタイミングを取得します。

これらの API から提供される情報量は膨大ですが、ウェブサイトにアクセスしたユーザーからこれらのタイミングを収集できるため、フィールドでの読み込みパフォーマンスを測定するうえで重要な要素となります。

ネットワーク リクエストのライフとタイミング

ナビゲーションとリソースのタイミングを収集して分析することは、ネットワーク リクエストの一瞬の生活を事後に再構築するという点で、考古学のようなものです。概念を可視化できる場合もあります。ネットワーク リクエストについては、ブラウザのデベロッパー ツールが役に立ちます。

Chrome の DevTools に表示されるネットワークのタイミング。示されているタイミングは、リクエストのキューイング、接続ネゴシエーション、リクエスト自体、レスポンスの色分けのバーです。
Chrome DevTools のネットワーク パネルでネットワーク リクエストを可視化。

ネットワーク リクエストには、DNS ルックアップ、接続の確立、TLS ネゴシエーション、その他のレイテンシの原因など、明確なフェーズがあります。これらのタイミングは DOMHighResTimestamp として表されます。ブラウザに応じて、タイミングの粒度はマイクロ秒単位の場合もあれば、ミリ秒単位の切り上げもあります。これらのフェーズと、ナビゲーション タイミングとリソース タイミングとの関連性を詳しく確認しましょう。

DNS ルックアップ

ユーザーが URL にアクセスすると、ドメイン ネーム システム(DNS)がクエリされ、ドメインが IP アドレスに変換されます。このプロセスにはかなりの時間がかかる場合があります。この時間は、現場で測定することをおすすめします。Navigation Timing と Resource Timing には、DNS 関連の 2 つのタイミングが公開されます。

  • domainLookupStart は DNS ルックアップが開始されるときです。
  • domainLookupEnd は DNS ルックアップが終了する時刻です。

DNS ルックアップの合計時間を計算するには、終了時間の指標から開始時間の指標を差し引きます。

// Measuring DNS lookup time
const [pageNav] = performance.getEntriesByType('navigation');
const totalLookupTime = pageNav.domainLookupEnd - pageNav.domainLookupStart;

接続ネゴシエーション

読み込みパフォーマンスに影響するもう一つの要因は、接続ネゴシエーションです。接続ネゴシエーションは、ウェブサーバーへの接続時に発生するレイテンシです。HTTPS が関係する場合、このプロセスには TLS ネゴシエーション時間も含まれます。接続フェーズは次の 3 つのタイミングで構成されます。

  • connectStart は、ブラウザがウェブサーバーへの接続を開き始めるタイミングです。
  • secureConnectionStart は、クライアントが TLS ネゴシエーションを開始するタイミングをマークします。
  • connectEnd は、ウェブサーバーへの接続が確立されたときです。

合計接続時間の測定は、合計 DNS ルックアップ時間の測定と同様です。終了時間から開始時間を差し引きます。ただし、追加の secureConnectionStart プロパティがあり、HTTPS が使用されていない場合や接続が永続的である場合0 になります。TLS ネゴシエーション時間を測定する場合は、次の点に注意してください。

// Quantifying total connection time
const [pageNav] = performance.getEntriesByType('navigation');
const connectionTime = pageNav.connectEnd - pageNav.connectStart;
let tlsTime = 0; // <-- Assume 0 to start with

// Was there TLS negotiation?
if (pageNav.secureConnectionStart > 0) {
  // Awesome! Calculate it!
  tlsTime = pageNav.connectEnd - pageNav.secureConnectionStart;
}

DNS ルックアップと接続ネゴシエーションが終了すると、ドキュメントとそれに依存するリソースの取得に関連するタイミングが関係します。

リクエストとレスポンス

読み込みパフォーマンスは、次の 2 種類の要因の影響を受けます。

  • 外的要因: レイテンシや帯域幅などが外的要因です。ユーザーはどこからでもウェブにアクセスできるため、ホスティング会社と CDN を選択する以外は、(ほとんど)制御できません。
  • 本質的要因: Google が制御できる、サーバーサイド アーキテクチャやクライアントサイド アーキテクチャ、リソースサイズ、それに合わせて最適化する能力などがこれに該当します。

どちらの要因も読み込みのパフォーマンスに影響します。これらの要素に関連するタイミングは、リソースのダウンロードにかかる時間を表すため、非常に重要です。Navigation Timing と Resource Timing はどちらも、次の指標を使用して読み込みパフォーマンスを示します。

  • fetchStart は、ブラウザがリソースのフェッチを開始したとき(リソース タイミング)またはナビゲーション リクエストのドキュメントを取得したとき(ナビゲーション タイミング)を示します。これは実際のリクエストに先行し、ブラウザがキャッシュをチェックするポイントです(HTTP インスタンスや Cache インスタンスなど)。
  • workerStart は、Service Worker の fetch イベント ハンドラ内でリクエストの処理が開始されたときのマークです。現在のページを制御している Service Worker がない場合、これは 0 になります。
  • requestStart は、ブラウザがリクエストを送信したときです。
  • responseStart は、レスポンスの最初のバイトが到着したときです。
  • responseEnd は、レスポンスの最後のバイトが到着したときです。

これらの時間データにより、Service Worker 内のキャッシュ ルックアップやダウンロード時間など、読み込みパフォーマンスのさまざまな側面を測定できます。

// Cache seek plus response time of the current document
const [pageNav] = performance.getEntriesByType('navigation');
const fetchTime = pageNav.responseEnd - pageNav.fetchStart;

// Service worker time plus response time
let workerTime = 0;

if (pageNav.workerStart > 0) {
  workerTime = pageNav.responseEnd - pageNav.workerStart;
}

リクエストとレスポンスのレイテンシの他の側面を測定することもできます。

const [pageNav] = performance.getEntriesByType('navigation');

// Request time only (excluding redirects, DNS, and connection/TLS time)
const requestTime = pageNav.responseStart - pageNav.requestStart;

// Response time only (download)
const responseTime = pageNav.responseEnd - pageNav.responseStart;

// Request + response time
const requestResponseTime = pageNav.responseEnd - pageNav.requestStart;

その他の測定方法

Navigation Timing と Resource Timing は、前述の例で説明した以上の用途に役立ちます。次のようなタイミングも、確認する価値があります。

  • ページ リダイレクト: リダイレクトは、特にリダイレクト チェーンの中では、待ち時間が長くなる原因とは見過ごされがちです。レイテンシは、HTTP から HTTPs へのホップや、302/キャッシュに保存されていない 301 リダイレクトなど、さまざまな方法で追加されます。redirectStartredirectEndredirectCount のタイミングは、リダイレクトのレイテンシの評価に役立ちます。
  • ドキュメントのアンロード: unload イベント ハンドラでコードを実行するページでは、次のページに移動する前に、そのコードを実行する必要があります。unloadEventStartunloadEventEnd は、ドキュメントのアンロードを測定します。
  • ドキュメントの処理: ウェブサイトから非常に大きな HTML ペイロードが送信されない限り、ドキュメントの処理時間は重要ではありません。この状況に当てはまる場合は、domInteractivedomContentLoadedEventStartdomContentLoadedEventEnddomComplete のタイミングが適しています。

コードでタイミングを取得する方法

これまでの例ではすべて performance.getEntriesByType を使用していますが、パフォーマンス エントリ バッファをクエリする方法は他にもあります(performance.getEntriesByNameperformance.getEntries など)。簡単な分析のみが必要な場合は、これらの方法で問題ありません。ただし、他の状況では、多数のエントリを反復処理したり、パフォーマンス バッファを繰り返しポーリングして新しいエントリを見つけたりすることで、メインスレッドの作業が過剰になる可能性があります。

パフォーマンス エントリ バッファからエントリを収集するには、PerformanceObserver を使用することをおすすめします。PerformanceObserver はパフォーマンス エントリをリッスンし、バッファに追加されるときに提供します。

// Create the performance observer:
const perfObserver = new PerformanceObserver((observedEntries) => {
  // Get all resource entries collected so far:
  const entries = observedEntries.getEntries();

  // Iterate over entries:
  for (let i = 0; i < entries.length; i++) {
    // Do the work!
  }
});

// Run the observer for Navigation Timing entries:
perfObserver.observe({
  type: 'navigation',
  buffered: true
});

// Run the observer for Resource Timing entries:
perfObserver.observe({
  type: 'resource',
  buffered: true
});

このタイミング収集方法は、パフォーマンス エントリ バッファに直接アクセスする場合と比べると不便に感じるかもしれませんが、ユーザー向けの重要な目的を果たさない作業でメインスレッドを拘束するよりも優れています。

自宅に電話する方法

必要なタイミングをすべて収集したら、エンドポイントに送信して詳細な分析を行うことができます。これを行うには、navigator.sendBeacon を使用するか、keepalive オプションが設定された fetch を使用します。どちらの方法でも、指定されたエンドポイントにリクエストが非ブロッキングで送信され、必要に応じて、現在のページ セッションを超える時間リクエストがキューに追加されます。

// Check for navigator.sendBeacon support:
if ('sendBeacon' in navigator) {
  // Caution: If you have lots of performance entries, don't
  // do this. This is an example for illustrative purposes.
  const data = JSON.stringify(performance.getEntries());

  // Send the data!
  navigator.sendBeacon('/analytics', data);
}

この例では、JSON 文字列は POST ペイロードで届きます。このペイロードは、必要に応じてデコード、処理、アプリケーション バックエンドに保存できます。

まとめ

指標を収集したら、そのフィールドデータをどのように分析するかはご自身で判断してください。フィールドデータを分析する際は、有意な結論を導くために、以下の一般的なルールに従ってください。

  • 平均値を避ける。平均値は、1 人のユーザーのエクスペリエンスを表すものではなく、外れ値によって歪曲される可能性があるためです。
  • パーセンタイルに依存します。時間ベースのパフォーマンス指標のデータセットでは、値が小さいほどよい。つまり、低いパーセンタイル値を優先すると、最も速いエクスペリエンスにのみ注意を払うことになります。
  • 値の長尾を優先する。75 パーセンタイル以上のエクスペリエンスを優先すると、最も遅いエクスペリエンスに集中することになります。

このガイドは、ナビゲーションやリソース タイミングに関する網羅的なリソースではなく、出発点として使用してください。以下に、お役に立つ可能性のあるリソースをご紹介します。

これらの API と、API が提供するデータにより、読み込みパフォーマンスが実際のユーザーにどう体験されているのかをより深く理解できるようになるため、実際の読み込みパフォーマンスの問題を自信を持って診断し、対処できるようになります。