はじめに
Webページの読込パフォーマンスについて、前回 、単純なWebページの読込時間(PLT)から、その過程を含めた評価方法(Speed Index) 、コンテンツのメインとなる巨大な画像リソース(Hero Image)といった、さまざまなメトリクスについて紹介しました。Webページ読込のパフォーマンスは、JSやCSS、画像などのリソースが互いに干渉しあい、ブロッキングを起こすことで劣化するのです。
後篇では、Webが扱うリソースをより低いレイヤーから考えてみましょう。前篇はあくまでHTMLというレベルでの制約(ブロッキング)と最適化の話でしたが、本記事ではHTTP以下の制約とその最適化に目を向けます。その対策は、通信時に使われるTCPコネクションをいかに効率化するのかという点と、キャッシュをいかに有効活用するのかという点の、2つの観点から整理できます。
TCPコネクションの有効活用
リソースの結合
シャーディング
SPDYとHTTP/2
キャッシュの有効活用
HTTP Cache(RFC2616)
PreloadとResource Hints
AppCacheとService Worker
これらについて、掘り下げてみてみましょう。
1. TCPコネクションの有効活用
Webでは、サーバとクライアントの間をTCPと呼ばれるプロトコルを通じて接続します。
TCPは決して効率のよいプロトコルとは言えません。接続が開始されるまでに、DNSによる名前解決、TLSの鍵交換、そしてTCPのスリーウェイハンドシェイクと、様々な前処理を行い、多くの時間を要します。また一般的なデフォルト設定のOSは、TCPはスロースタートと呼ばれるアルゴリズムを採用しています。接続直後は、ネットワークのポテンシャルを充分には発揮できません。
サイズが小さなリソースを大量にサーバから取得するようなケースでは、TCPのオーバヘッドが大きく、読み込み速度が問題となります。1つのTCPコネクションで同時にダウンロードできるリソースは1つだけですが、先に挙げたブロッキングの制約も踏まえると、これが問題とはならなかったりもします。こうした経緯から、HTTP/1.1の時代、小さなリソースは極力結合され、今では慣習として行われるような状況です。
1-a. リソースの結合
リソースの結合では、Webの仕様を活用して出来る限り多くのリソースをつなぎ合わせることで、Webページが含むリソースの数を減らします。何をつなげるかによって、その呼び名も手法も大きく異なってきます。
JavaScriptやCSSは、メンテナビリティを配慮し複数のファイルに分割されることがあります。しかしながら、これらはサイズが小さいことが多く、読み込み順序が制限されていることもあります。ファイルを結合させることで、パフォーマンスを改善させることを「Concat」と呼びます。
ファイルサイズが小さくなるのはJSやCSSだけではありません。アイコンなどに活用されるような画像ファイルもまた、小さく大量になりがちです。これらを1つのファイルに結合し、CSSを使ってブラウザ上で切り取って使おうというアプローチもあります。それを「CSSスプライト」と呼びます。
図1 リソース結合
これらの同じ種類のリソースを結合するというアプローチは、TCPのオーバヘッドを削減するうえで非常に有益です。ただ、最も効果的なものは、そもそも新しいTCPコネクションを作らないということです。HTMLドキュメント自体にJSやCSSを埋め込むことで、ブロッキングの時間を最小化するこのアプローチを「インライン化」と呼び、ATFの最適化においては非常によく活用されます。
リソースの結合によるパフォーマンスの最適化は、ブラウザから見れば少々無理をしたやり方であり、けっして正攻法とは言えません。真面目に扱おうとすれば、非常に煩雑なものになります。開発や運用の現場では、ツールを使って補うことが必須になります。
代表的なものだと、「 Grunt 」や「Gulp 」といったタスクランナーが挙げられます。これらとプラグインを組み合わせ、開発などのワークフローに組み込んでいくとよいでしょう。
1-b. シャーディング
さてここまで、TCPはオーバヘッドが大きいと説明してきました。しかし、このオーバヘッドは常に悪になるとは限りません。
そもそもHTTP/1.1のTCPでは、1つのコネクションに対して同時に1つのリソースのみしかダウンロードできません。Webページに含まれるリソースは100や1,000を超えることもあり、複数のファイルを並行してダウンロードしたほうが高いパフォーマンスが得られることもあります。しかしながら、ブラウザが1つのドメインに対して接続できるコネクション数は最大6つまでというのがデファクト標準になっています。
こうした制約を回避するために用いられるのが「ドメインシャーディング」です。ドメインシャーディングでは、並列ダウンロードさせたいリソースを「a.example.com」「 b.example.com」「 c.example.com」といった感じでドメイン名を変えて接続させることで、最大6つという制約を回避させます。インフラレベルでの改善が求められややハードルは高いのですが、CDNなどの他社サービスを活用するケースにおいては、当たり前のように行われている対策です。
図2 シャーディング
1-c. SPDYとHTTP/2
インターネットを支えるベースとなるプロトコルであるIP(インターネットプロトコル)の弱点は、それを構成している技術がサービス提供者側で制御しきれないことです。クライアントとサーバ間には、様々な企業が提供している通信機器やネットワークがあり、これらが必ずしもセキュリティやパフォーマンスを担保してくれるとは限りません。
こうした状況を鑑みて、Webではサービス提供者側でコントロール可能な、上位レイヤーにて品質を担保する傾向にあります。GoogleがHTTPSを全てのWebサイト・サービス提供者へ必須にするよう呼びかけるのもその一つです。パフォーマンスにおいても同様の流れが生じています。
これまでGoogleでは、「 リソースの結合」や「シャーディング」といった方法を開発TIPとして推進してきました。ただそれは、HTTPSのような正攻法とは言い難く、泥臭い対策が求められます。しかし、彼らはブラウザを開発しているという強みを活かし、HTTPそのものを再発明し、パフォーマンスを改革しようとしました。それが「SPDY」です。
SPDYはのちに、IETFにて次世代プロトコルを考案するためのベースとして活用され、2015年春にRFC化が完了しました。これが「HTTP/2 」です。
HTTP/2は、TCPコネクションを1つに絞ることでオーバヘッドを削減しつつも、各リソースのダウンロードをストリームとして扱うことで並列化を可能としています。HTTP/1.1の時代にTCPコネクション周りで抱えていた問題は、全て解決されると期待されています。また、Head-of Line Blockingと呼ばれる、パケットロス時のパフォーマンス劣化問題も低減しており、従来では補えなかった問題も改善しています。
最近ではHTTPの問題だけでなく、TCPの抱えている問題もサービス提供者側でコントロールできるようにしようという流れも起きています。それが「QUIC 」です。QUICではUDPを使うことで、従来OS側で実現していたTCPの持つ機能を、ブラウザ/Webサーバ上で行えるようにします。こちらは影響範囲も大きく一筋縄ではいかなさそうですが、今後のWebに大きな影響を与える可能性があるでしょう。
2. キャッシュの有効活用
TCPコネクションの最適化は、リソースの読込を大きく変え、パフォーマンスを向上させます。しかしながら、Webには強力なキャッシュ機構が備わっており、同じサイトへの二度目のアクセス(セカンドビュー)においては、一度目のアクセス(ファーストビュー)とは比べ物にならないほどの高いパフォーマンスを発揮できます。
Webのパフォーマンス計測においても、ファーストビューとセカンドビューは区別して行うのが一般的です。そしてセカンドビューにおいては、ファーストビューよりもはるかにできることの幅が広かったりします。ここで、網羅的に取り上げてみましょう。
2-a. HTTP Cache(RFC2616)
HTTPには古くから、Request/Responseヘッダーを通じてブラウザ上のキャッシュを制御する仕組みがあります。これを「HTTP Cache 」といいます。
HTTP Cacheでは、ブラウザ内に置かれたキャッシュの生存期間を判断する手段として、2通りのパターンがあります。1つは、サーバ側に問い合わせてタイムスタンプ(If-Modified-Since)やシグネチャ(ETag)を元に判定する方法。もう1つが、ブラウザだけで有効期限(Expires/max age)を元に独断で判断する方法です。
図3 キャッシュの生存期間を判断
おおよそのミドル製品では、前者のみがデフォルトで有効になっています。意識しないうちに、この機能の恩恵を受けている可能性もあるでしょう。
2-b. PreloadとResource Hints
HTML5のブームが起きてからは、Web標準としても新しい機能を追加し、キャッシュを強化しています。
先ほどのHTTP Cacheでは、一度読み込んだことがあるリソースを出来る限り効率的に動かそうというアイデアでした。ただ、考えてみてください。今見てるWebページだけでなく、これから使う可能性があるWebページのリソースも事前に読み込んでおくことができれば、サービス全体としてのパフォーマンスの最適化が期待できるでしょう。これを解決するのが、「 Preload 」や「Resource Hints 」と呼ばれるWeb標準です。
これらはlinkタグを通じて、リソースの先読みであったり、DNSやサーバとのTCP接続開始処理を事前に行ったり、あるいは各リソースについて重み付け係数を指定できたりと、通信レイヤーに関わる様々な機能を提供しています。IE11がリリースされた直後の頃は、imgなどのリソースに関わる要素に対してlazyloadと呼ばれる属性を指定することでコントロールが行えていましたが、そこから仕様が二転三転し、今となっては全く違う姿へと変えています。
2-c. Service WorkersとBackground.sync
モバイルが利用者のメインコンピュータへと変わりつつある中、ネットワークの扱い方もまた、従来のPC型とは異なる課題に悩まされつつあります。特に大きな変更点としては、バッテリーの制約と、コンテンツが能動化したことの2点でしょう。
モバイル時代の通信において、ネットワークインターフェースの起動回数は少なく、そして起動時間も短くする必要があります。バッテリー消費が大きいからです。通信処理を発生させるタイミングにも注意が必要です。これに加えて、モバイル固有のプッシュ通知機能が広がり、コンテンツにおいてもエンゲージメントを高めるべく通知ありきのデザインが求められるようになりました。
キャッシュの仕組みも、ここまで「一度取得したことがあるものをキャッシュ(HTTP Cache) 」から「事前に取得しキャッシュする(Preload/Resource Hints) 」へと進化してきました。しかし、昨今のモバイルが主導の時代を鑑みて「通信の状況に合わせてキャッシュを活用する」や「能動的にコンテンツを端末へキャッシュさせに行く」といった、従来想定していなかったようなキャッシュの制御が求められています。
こうした時代のニーズを吸収しているのが「Service Workers 」です。Service Workersは、ブラウザ内で動作するローカルプロキシのように振る舞い、ネットワークの状況に合わせてキャッシュを活用します。また、プッシュメッセージを受け取った際にも、通信の状況に応じてコンテンツのデータを取得する(Background.sync)といった動きをします。日々進化しており、まだまだこれからに期待できる技術と言えます。
最後に
Loadは1秒という時間の中に複雑な処理をいかに詰め込むかが戦いでしたが、対してAnimationはいかに処理をブラウザ側へ委ね「60fps」を死守するのか、がミソになってきます。CSS AnimationやWeb Animationといったユーザワールドでの解決方法、Task Schedulerのようなブラウザ内で起きている変化について、掘り下げて紹介します。
最終回である次回は、RAILのA、Animationのパフォーマンス改善について解説します。
それではまた、お会いしましょう。