HTML5Experts.jp

Navigation Timingだからできる、Webアプリを俯瞰したパフォーマンス計測(2/3)

前回の記事でもお伝えしたとおり、ブラウザのメカニズムが、TATの計測を複雑にしています。その事情について、まずTATの「開始」をどこにするかについて考え、紐解いてみましょう。Web標準で定義するところのブラウザの動作について、少しだけデフォルメさせた処理モデルを使って、全体の流れを解説します。以下の図の赤字はtimingオブジェクト配下にあるプロパティ、数字が振ってある矢印は処理の流れです。

0. ナビゲーション開始

Webページの読み込み動作を、「ナビゲーション」と呼びます。ブラウザは、ネットワーク上のHTMLドキュメントにアクセスすると、Webページを構成する様々な情報を削除(アンロード)します。そして、新しくHTMLドキュメントを読み込み、DOMを構成し、JavaScriptやCSS、画像などのリソースを取得しながらWebページの描画を進めていきます。この一連の動作が、ナビゲーションです。

近年は、「SPA(Single-page Application)」と呼ばれるアーキテクチャがあり、DOM操作により画面遷移を擬似的に表現する手法もありますが、この仕組みではナビゲーションを開始できません。あくまで、JavaScript APIやプラグインの力を借りず、ブラウザの機能を使ってWebページを遷移させることで、ナビゲーションは開始されます。

ナビゲーションは、ハイパーリンクのクリックもあれば、Formを使ってサブミットすることでも発生します。戻る/進むボタンやリロードボタンを押下した際も、同様です。こうした、様々なナビゲーションを発生させるアクションは、ブラウザのキャッシュの動作、ナビゲーション全体の動作への影響が大きいため、Navigation Timingを扱う際に理解することが求められます。

Web標準では、こうしたナビゲーションの種類を「PerformanceNavigation」として以下のように分類しています。まずは、この定義を中心としてその違いを把握してみましょう。

「TYPE_NAVIGATE」は、ハイパーリンクをクリックした際や、フォームをサブミットした際に発生する、一般的なナビゲーション動作です。ナビゲーションバーへURLを入力しEnterキーを押下した際、ブックマークを開いた際もこの動作であり、基本はこのアクションが選択されると想定すべきでしょう。後に説明する、「RFC 2616」のHTTP Cacheが最大限に活用されるのも、このナビゲーション動作です。

「TYPE_BACK_FORWARD」は、基本的には「戻る/進む」ボタンを押下した際に発生します。Web標準で言うところの「履歴移動(History Traversal)」により発生したナビゲーションです。ただ、「フラグメント識別子(Fragment Identifier)」を利用したURLを扱った場合、ナビゲーションを伴わないWebページ内の移動を行うことがあります。また、Webページが一定の条件を満足すると、ナビゲーション発生時に、前のWebページ全体をそのままキャッシュさせることで、戻るボタンのパフォーマンスを改善しようとします。このような動作を行なった場合、戻るボタンを押下してもナビゲーションは発生しません。

「TYPE_RELOAD」は、「リロードボタン(再読み込み)」を押下した際に発生します。ブラウザによってCtrl/Shiftとリロードボタンを同時押しすることで、HTTP Cacheの一切を破棄しWebページの再読み込みを行う「スーパーリロード(強制再読み込み)」と呼ばれる機能があります。Navigation Timingではこれを区別することなく「TYPE_RELOAD」として扱いますが、実際のキャッシュの動作は大きく異なるものとなるため、明確に区別する必要があります。

「TYPE_RESERVED」については未定義であり、ブラウザでWebページを扱っている範囲では、あまり意識する必要はありません。

1. アンロード

ナビゲーションが開始され、標準的なブラウザはまず「1. アンロード」という処理を行います。ブラウザは、今表示しているWebページのリソースを破棄し、次のWebページを表示する準備をするわけです。DOMの情報を持つDOM Contentや、JavaScriptの機能の初期化を行います。

Web標準では、この処理を「unloadイベント」として捕捉でき、Navigation Timingを利用してイベント発生前後の時間を取得することができます。ただ、このタイミングは常に一定しているとは限りません。(※ 注1)表面的には、アンロードはこのタイミングで発生しているように見せてはいますが、実装ではブラウザのレンダリングエンジンによってバラツキがあり、この処理の負荷が、この後説明するDOM読み込みのタイミングに大きな影響を与えることがあります。

また、「戻る/進む」ボタンを押下した場合の動作も独特です。先ほども少し説明しましたが、Webページは一定の条件を満足すると、アンロードを行わずメモリ上にWebページをまるごとキャッシュさせます。この動作は、例えばIE11だとデベロッパーセンター、FirefoxだとMDNのドキュメントに仕組みが掲載されており、ブラウザごとにやや異なる思想を持っていることも把握できるはずです。HTML5の「pageshow/pagehide」イベントで捕捉でき、ある程度の制御が可能なわけですが、基本的にこの動作、ナビゲーションが発生しないのですから、Navigation Timingのプロパティの値も更新されません。

2. キャッシュ確認

標準的なブラウザがアンロードを終わらせると、「2. キャッシュ確認」を行います。Webページの表示に必要なHTMLドキュメントを、キャッシュの中から探すわけです。キャッシュと言っても、ブラウザのみで有効性判断ができるものあれば、サーバに問い合わせを行わないと判断できないものもります。ここでチェックされるのは、あくまで前者のみです。HTML5の「AppCache」や、「RFC 2616(13.5)」で定義されたHTTPヘッダのプロパティ「Expires(14.2)」「Cache-Control max-age(14.9)」によるHTTP Cacheなんかは古くから活用されているため、ご存知の方も多いでしょう。

AppCacheは、常にこの段階で読み込みが行われます。しかし、HTTP Cache(Expires)により得られるゆるいキャッシュは、TYPE_NAVIGATEの場合のみ、ブラウザ内のキャッシュを利用します。この仕様は動作チェックの際に問題となることがあり、誤ってリロードボタンを押下してテストしようものなら、テスト担当者は一日を無駄に過ごすことになります。(※ アドレスバーにフォーカスを当ててEnterキーを押下、がキャッシュのテストの鉄則です!)

Chromeだけは、やや動作が例外的です。Chromeは、HTMLドキュメント内から参照されるJS/CSS/画像ファイルのようなリソースでも無い限り、HTTP Cacheによるブラウザ内のキャッシュを利用してくれません。XHR(XMLHTTPRequest)についても同様で、Chromeだけは必ずサーバへのアクセスを伴います。そもそも、Webページはサーバサイドのプログラムで動的に生成するのが殆どなので、HTMLドキュメントのキャッシュは期待できないものと見なすべきでしょう。

3. 名前解決

標準的なブラウザは、有効なキャッシュを発見できなかった場合、HTMLドキュメントが置かれているサーバの場所を特定するため「3. 名前解決」を行います。

名前解決については、Linux/OS X/iOS上のブラウザについては、基本的にDNS名として扱い名前解決を行います。しかし、Windows OS上のブラウザは、微妙に同じ動作にはなりません。Windows OS上のブラウザは、ドメイン名をフルコンピュータ名というやや特殊な概念として扱い、NetBIOS(WINS)名とDNS名の2つの名前の概念を扱うハイブリッドな名前解決を行います

これは、ブラウザの仕様というより、OSの持つTCP/IPモジュールの仕様の違いです。IEとFirefox/Chromeとで異なるAPIをコールしていますが、どちらも最終的には同じWinSock2の名前解決のメカニズムを利用します。名前解決を行うリゾルバにも、OSによる動作の違いがありますが、DNSを使っている限り、機能面において、ブラウザからはあまり意識する必要がありません。

インターネット向けでは、各プロバイダが提供するDNSサーバの性能が関わってくることもあります。DNSもやはり、キャッシュが要です。DNSを構成する各ノードのキャッシュの動作次第で、性能は大きく変化します。TTL設定のチューニング、DNS Prefetchを活用して対策してみると良いでしょう。

キャッシュだらけ!

ナビゲーションの動作のうち、どこをTATの開始とすべきか?もうお分かりと思いますが、キャッシュだらけで、前提条件から疑いにかからなくてはいけません!Webページ自体の(アンロード絡み)のキャッシュ、HTML5 AppCacheやRFC 2616によるブラウザ側のキャッシュ、名前解決絡みのキャッシュと、まだWebサーバにアクセスしていないというのに、これだけのキャッシュが絡んでいます。

最初にご紹介した「navigationStart」は、こうした前の画面の動作を含んだ計測値です。アンロードや、この後説明するリダイレクトを含めるとなると、開始をどこに置くのかというのもまた難しくなってきます。

TATの本当に難しさはここだけではありません。TATの「終了」の扱い、これも難しかったりします。ブラウザのメカニズムを掘り下げて追ってみましょう。

「Navigation Timingだからできる、Webアプリを俯瞰したパフォーマンス計測(3/3)」に続きます。(※5/9公開予定)

※注1 : 実装では、Chromeのアンロードはこのタイミングには行われていません。気になる方は、Chronium35であれば./core/loader/DocumentLoader.cpp(startLoadingMainResource)を確認してみると良いでしょう。PerformanceTiming::markNavigationStart()の後にPerformanceTiming::markFetchStart()を呼び出しており、アンロードは遅延して実行していることが読み取れます。なお、Chromeのアンロードは、HTMLドキュメントのHTTP受信完了時に発生します。このあたりは、./core/loader/DocumentLoader.cpp(finishedLoading)あたりが動作を理解するのに適しており、commitIfReady()の実装を掘っていくと、./core/loader/FrameLoader.cpp FrameLoader::commitProvisionalLoad→FrameLoader::closeURL…と進み、最終的にunloadイベント発火のタイミングをマークしている処理に到達します。