ハイパフォーマンスブラウザネットワーキングを読みました(HTTP)
最近ハイパフォーマンスブラウザネットワーキングの10章~12章を読んだのでまとめました。
どんな本?
- ブラウザに関連する、インターネットで使用される様々なネットワーク技術をまとめたもの
- ハイパフォーマンスを誇るアプリケーションを構築するためには、なぜネットワークがそのような挙動を見せるのか理解する必要がある
- 「良い開発者はどのように動作するか知っている。素晴らしい開発者はなぜ動作するかを知っている」
- Webアプリケーションを提供するには、ブラウザとネットワークの動作の関連性について、硬い基盤となる知識が必要とされる
- ゴールはネットワークについてすべての開発者が知っておくべきすべてを説明すること
- どのようなプロトコルが用いられてる?
- どのような制限を持つ?
- アプリケーションをその基盤ネットワークに対して最適化する方法はどのようなもの?
- ブラウザにが提供するネットワーク機能とその使いどころは?
目的
ハイパフォーマンスを誇るアプリケーションを構築するために、なぜネットワークがこのような挙動なのかを理解し、理由が述べられるようになる。(なりたい...)
とりあえず10章~12章に絞って読みました。
10章 Web パフォーマンス入門
パフォーマンス最適化のプロセスの大部分は、システム内で明確に分離している。 制限と制約を持った各レイヤーの間に存在する相互作用を理解し紐解くこと。
すべての異なるレイヤーの相互作用を最適化すること?
相互依存しているいくつかの方程式を説いて回答を導き出すことではなく、場合によって多数の解が存在する。
それぞれのパフォーマンスのベストプラクティスを数値化・分析する前に一歩下がって、問題そのものを定義しておくことが重要。
- モダンWebアプリケーションとは?
- どのようなツールが利用可能なのか?
- Webアプリケーションをどのように計測するのか?
など
モダンWebアプリケーションの解剖学
モダンWebアプリケーションとは?
以下のサイトが参考になる。 主要Webサイトのコンテンツを自動分析してレポートしている。 https://httparchive.org/reports/state-of-the-web
webアプリケーションにはインストールプロセスがない。 URLを入力し、enterキーを押すだけで使える状態になる
最高のweb体験提供するには、数百のリソース、数MBのデータ、いくつもの異なるホスト。 これらすべてを数百ミリ秒以内に。
スピード、パフォーマンス、そして人間の知覚
生活のペースが歴史上一番早くなっている。 にもかかわらず、反応時間は常に一定。
アプリケーションが瞬時に反応したと感じさせられるためには、ユーザの入力に対して知覚できるレスポンスを数百ミリ秒のうちに提供しなければならない。
一般的なWebページのリクエストにかかるDNSルックアップ、TCPハンドシェイク、Webページリクエストに通常かかるいくつかのパケット往復時間を合計してみる
100-1000ミリ秒というレイテンシの予算はネットワークオーバーヘッドだけで、すべてではないにせよ、その大半が簡単に消化せれてしまう。
リソースのウォーターフォールチャートを分析する
Webパフォーマンスの議論はリソースのウォーターフォールチャートに言及するまで完結しない。
ウォーターフォールチャート → 増減を表すのに便利なグラフ
WebPageTestを使って診断する。
repro.ioの測定結果
Waterfall View
- fontの読み込みが多い印象
- jsの読み込みが多い印象
- ドメイン数多い印象
- DNS Lookup
- Initial Connection
- TCP接続の確立
- SSL Negotiation
- SSLを介してリソースをセキュアに読み込んでいる場合、ブラウザがその接続を確立している時間
- Time to First Byte
- リクエストがサーバーに送られ、サーバーがそれを処理して、必要な情報を送信し始め、レスポンスの最初の1バイトがブラウザに届くまでにかかる時間
パフォーマンスの柱:演算、レンダリング、ネットワーク
webプログラムの実行の主要なタスク
- リソースの取得
- ページのレイアウトとレンダリング
- JavaScriptの実行
レンダリングとスクリプトの実行モデルはシングルスレッド、インターリーブ型 同時並行して変更を加えることはできない
早く効率的なネットワークリソースの配信がブラウザで動作するすべてのアプリケーションのパフォーマンスにおいて必要
より大きい帯域幅は効果なし
動画よりもはるかに小さいWebアプリケーションがなぜ難題?
パフォーマンスのボトルネックとしてのレイテンシ
SPDYプロトコルの作者の一人、Mike Belsheによる定量的調査
一般的にインターネットをスピードアップするには、
- RTT(Round-Trip Time) を下げる方法を探すべき
- ページロードに必要とされるパケット往復の数を減らすこと
レイテンシ データ転送における指標のひとつで、転送要求を出してから実際にデータが送られてくるまでに生じる、通信の遅延時間のことをいいます。 この遅延時間が短いことをレイテンシが小さい(低い)、遅延時間が長いことをレイテンシが大きい(高い)と表現しています。
RTT(Round-Trip Time) 通信相手に信号やデータを発信してから、応答が帰ってくるまでにかかる時間。
ブラウザ最適化
パフォーマンスはブラウザベンダにおける最も重要な競争力の要素であり、ネットワークパフォーマンスがその重要な基準であれば、ブラウザが日々進化していることに驚くべきことではない。
ブラウザ最適化は2つの大きなカテゴリに分類される
- ドキュメント認識最適化
- 投機的最適化
- DNSの事前開発やホストへの事業接続を行い、ユーザーが行う可能性が高いアクションを予測する投機的最適化を行う
↑の最適化はユーザや開発者の代わりにブラウザが自動的に行う
内部的にこれらの最適化がどのように、なぜ行われているのかの理解が大切
開発者はどのようにブラウザを補助できるのか?
最初に、ページの構造と配信によく注意を払う
- CSSやJavaScriptのような重要なリソースはドキュメント上で可能な限り発見できるべき
- レンダリングとJavaScriptの実行をブロックしないために、CSSは可能な限り早く配信されるべき
- 重要度の低いJavaScriptはDOMとCSSOMの構築をブロックしないように後回しにすべき
- HTMLドキュメント上はサーバに徐々に解析されるため、ドキュメントはサーバ上で生成された次第、部分的にも随時送信されるべき
dns-prefetch
<link rel="dns-prefetch" href="//somewidget.example.com">
DNS事前解決
外部URLからリソースを取得する要素がある場合、ブラウザはdocumentの上部から解釈し、外部URLを見つけた時点で外部URLへリクエストを送ります。この時に、前もって名前解決を済ませておけば、どこにリクエストするのかがわかっているから処理するのが早い
subresource
<link rel="subresource" href="/css/style.css">
重要度は高いがページの後ろで読み込まれるリソースの優先プリフェッチ
subresourceは同一ページ内で使用する任意のリソースを裏で読み込んでおくことができる。
prefetch
<link rel="prefetch" href="//example.com/future-image.jpg">
リソースのプリフェッチ
ユーザーが次に訪問する可能性が高いページを開発者がわかっている場合、リソースを前もって取得しておくことができる。ただし、JSやCSSなどキャッシュ可能なリソースに限られる。
prerender
<link rel="prerender" href="//example.com/future-page.html">
指定ページのプリレンダリング
指定したページのCSSを読み込み、JSを実行、ページ全体の不可視バージョンを作成する。
10章まとめ
DNS、TCP、SSLの遅延はほとんどのユーザと、Web開発者にはまったく透過的で、ネットワーク層で処理される。 ブラウザがこれらの往復を予測することを補助することによって、これらのボトルネックを排除しより速くより良いWebアプリケーションにつながる
11章 HTTP1.x
HTTP1.0の最適化はHTTP1.1にアップグレードすること この標準は多くの重要なパフォーマンス強化や機能を追加した
- 接続の再利用を可能とする、永続的接続
- レスポンスのストリーミングを可能とするチャンク化された転送エンコード
- 並列リクエストの処理を可能とするリクエストパイプライン
- リソース要求のバイトレンジ指定を可能にするバイト単位の転送
- 改善され、より効果的にしていされたキャッシュメカニズム
永遠の最適化項目
- DNSルックアップを減らす
- ルックアップ中はリクエストがブロックされる
- HTTPリクエスト数を減らす
- ページから不要なリソースを排除
- CDNを利用
- データを地理的に近い場所に配置することでTCP接続のネットワークレイテンシを劇的に減らせる
- Expiresヘッダを追加し、ETagを設定
- 同じリソースの取得のために何度もリクエストを送信しないように必要なリソースはキャッシュのしておくべき
- リソースをGzip圧縮する
- HTTPリダイレクトを避ける
HTTP1.1で追加されたパイプラインは実質的には失敗しており、基礎に亀裂を生じさせている 常に独創的であり続けるWeb開発者コミュニティが数々の自家製の最適化を発明 (ファイル結合、スプライト、インライン化)
しかしこれらの技術はHTTP1.1の制限を回避する一時しのぎのためのもの 本来はこのような最適化を気にすべきではない
キープアライブ接続の利点
HTTP1.1の主要なパフォーマンス向上の一つが持続的接続な接続、または、キープアライブ接続の採用
なぜこの機能がパフォーマンス戦略における重要な要素なのか?
キープアライブ
一つのTCP接続を一回のHTTP通信で切断せず、複数のHTTPリクエスト/レスポンスを送受信するよう維持する機能。
言うまでもなく、すべてのアプリケーションにとって持続的なHTTP接続は重要な最適化
HTTPパイプライン
HTTPパイプライン
一つのTCPコネクション上で、複数のHTTPリクエストを応答を待つことなく送信する技術である。 リクエストをパイプライン化することにより、ウェブページの読み込みが大幅に高速化される。
リクエストを早いうちに送信しておくことによって、それぞれのレスポンスをブロックすることなくもう一往復分のネットワークレイテンシを削減できる
しかし、HTTP1.1では多重化送信がサポートされていいないため、パイプラインはHTTPサーバや中間装置、そしてクライアントに様々な微妙かつドキュメント化されていない影響を及ぼす
しかし、パイプラインはサーバー側できちんとした対応がされていないと、ブラウザ側のリクエストを正しく処理できないということになり実装にも課題がありました。さらに、HTTPパイプラインには「サーバーはリクエストの順番通りにレスポンスを返さなければならない」という制限があります。
5個あるリクエストのうち1番目のリクエスト処理が遅い場合、2個目以降のレスポンスは待ち状態(ヘッドオブラインブロッキング)になり結果全体の速度が遅くなるといった課題がある。このような状況から、パイプラインはOPERAブラウザを除きほとんどのモダンブラウザはデフォルトでOFFになっており、残念ながらほとんど利用されていないのが現状。
- 遅いレスポンスが一つ存在すると、その後のすべてのリクエストがブロックされる
- 並列処理を行う際、サーバはパイプライン化されたレスポンスをバッファリングする必要があり、サーバのリソースを圧縮する可能性がある。
- レスポンスに失敗するとTCP接続が終了する場合があり、クライアントは未処理リクエストを再送して重複処理になる可能性がある
- 中間装置が存在する可能性がある場合、パイプラインの互換性を信頼できる形で検出することは難しい。
- 中間装置にはパイプラインをサポートしないものがあり、接続切断する可能性がある、
↑のような複雑さと、発生する問題に対する指針がHTTP1.1標準で示されてないために、ある程度の改善効果が見込めるにも関わらずHTTPパイプラインはほとんど採用されていない
複数のTCP接続を使用する
モダンブラウザでは、デスクトップでもモバイルでも、ホストあたり6接続まで並列使用できる。
なぜ?
接続数が多いほどクライアントとサーバのオーバヘッドは増大するが、リクエストの並列性による利点は増す。 ホストあたりの6接続はバランスをとった。
オーバーヘッド
付加的に発生した処理(負荷)のこと
ドメインシャーディング
シャーディング(分割)
HTTP1.xプロトコルが持つ欠如のために、ブラウザベンダは1ホストあたり6つまでのTCPストリームを管理する接続プールの採用と維持を強いられてきた。
HTTPArchiveによると、平均的なWebページは90以上のリソースで構成されている。 これらのリソースがすべて同じホストから配信されている場合は相当なキューイング遅延が発生。
すべてのリソースを同じホストから配信しなければならない?
すべてのリソースを同じホスト(www.example.com
)から配信する代わりに{shard1, shardN}.example.com
といった複数のサブドメインにシャーディング(分割)できる。
接続先のホスト名が異なるので
- ブラウザの接続制限を増加させてより高いレベルの並列性を手に入れることができる。
- より多くのドメインに分割することで、より並列性が高まる。
デメリット
- それぞれのホストについてDNSルックアップが必要になる
- サイト管理者がリソースをどのように分割配置するかを管理しなければならない
実際にはドメインシャーディングは過度に利用される傾向があり、数十の利用されていないTCP接続を低下させてしまう。 HTTPSを使用しなければならない場合はTLSハンドシェイクのために追加のパケット往復が発生し、更にコストが高くなる
まとめ
ドメインシャーディングの実施に際して以下を考慮する必要がある
- まずTCPの最適化
- ブラウザは6つまでの接続を自動的に開始
- それぞれのリソースの数、サイズ、そしてレスポンス時間がシャードの最適数を左右する
- クライアントレイテンシと帯域幅がシャードの最適数を左右する
- ドメインシャーディングはDNSのルックアップとTCPスロースタートによってパフォーマンスを低下させる可能性がある
ドメインシャーディングは合理的ですが、不完全な最適化。 まずリソースの数を減らしてリクエスト数を削減することで大きな利益を得ることができる。
プロトコルオーバーヘッドの計測と制御
HTTP1.0はリクエストとレスポンスのヘッダに書式を追加しメタデータの交換ができるようにプロトコルを拡張した
ブラウザが開始するHTTPリクエストじゃ500-800バイトのHTTPのメタデータを運んでいるい。 すべてのHTTPヘッダは無圧縮のプレーンテキストで送信されるため、すべてのリクエストで高いオーバーヘッドにつながる可能性がある。
無圧縮で繰り返し送信されるヘッダの転送量を減らすことで、パケット往復によるネットワークレイテンシを削減でき、パフォーマンス向上に繋がる。
ファイル結合とスプライト
リクエスト数を減らすことは、使用プロトコルやアプリケーションに関係なく最高の最適化。
リソースを一つのリクエストにまとめる
ファイル結合 複数のJavaScriptやcssファイルを一つのリソースにまとめること
スプライト 複数の画像を統合した画像
デメリット
複数の独立したリソースのバンドルはキャッシュのパフォーマンスやページの実行速度を低下させる。
- 統合されたファイルは、現在のページには必要ないリソースを含んでいる可能性がある
- どれか一つのファイルをアップデートするとキャッシュは無効化され、リソースのバンドルを再度ダウンロードする必要があり、オーバーヘッドが追加発生する
- JavaScriptとCSSはどちらも、転送が終了してから構文解析を開始し、実行する。したがってアプリケーションの実行を遅くする可能性がある。
まとめ
結合とスプライトはHTTP1.xプロトコル向けのアプリケーション層における最適化。 正しく使用された場合はかなりのパフォーマンス向上をもたらすが、アプリケーションの複雑さやキャッシュ時の注意、アップデート時のコスト、スクリプト、レンダリングの実行時間に悪い影響を与える可能性がある。
- アプリケーションは多くの小さなリソースのダウンロードによってブロックされているか?
- アプリケーションは特定のリクエストを結合することで利益を得ることができるか?
- キャッシュの粒度が落ちるとアプリケーションに悪い影響があるか?
- 結合された画像は高いメモリオーバヘッドに繋がるか?
- 実行が遅くなることで最初のレンダリングまでの時間に影響を与えるか?
リソースインライン化
ドキュメントに直接リソースを埋め込むことにより、リクエストの数を減らす
data:image/gif;base64,R0lGODlhAQABAAAAACw=
dataURIスキームを利用すれば、画像や音声、PDFファイルを埋め込める
dataURIは小さくて、汎用的ではないリソースに向いている。 インライン化されるとブラウザやCDNにキャッシュされない
リソースをインライン化する大まかな指針としては、リソースの大きさが2KB未満程度であること。
検討すべき基準は以下
- ファイルが小さく、特定のページにのみ配置される場合は、インライン化を検討
- ファイルが小さく、多くのページで再利用される場合は、バンドル化を検討
- ファイルが小さくても頻繁にアップデートされる場合は個別ファイルのまま扱う
12章 HTTP2.0
HTTP2.0はアプリケーションをより速く、よりシンプルに、そして堅牢にする
HTTP2.0の目標は、リクエストとレスポンスの多重化によるレイテンシの削減、HTTPヘッダフィールドの効率的な圧縮によるプロトコルオーバーヘッドの最小化、リクエスト優先度設定とサーバプッシュの実現
HTTP2.0はHTTPメソッド、ステータスコード、URI、などすべてのコアコンセプトを全く変更しない。
目的は最高のパフォーマンス
HTTP2.0の歴史、そしてSPDYとの関係。
SPDYはHTTP2.0ではない。 HTTP-WG内の様々な議論を経てSPDYがHTTP2.0の出発点に採用される
設計と技術的目標
HTTP1.xは実装のシンプルさを意識して設計される。 しかし残念ながら、実装のシンプルさによってアプリケーションのパフォーマンスが犠牲になる。
HTTP2.0はこの犠牲を取り戻すよう設計されている。
バイナリフレーミングレイヤー
HTTP2.0のすべてのパフォーマンス強化の中心となる存在は、バイナリフレーミングレイヤー。
ストリーム、メッセージ、フレーム
ストリーム
確立した接続内の双方向のフレームの流れ
メッセージ
フレームの完全なシーケンス。論理的なメッセージを構成する。
フレーム
HTTP2.0における通信の最小単位。それぞれのフレームはヘッダを持ち、ヘッダは最低でもそのフレームが所属するストリームを識別する
リクエストとレスポンスの多重化
HTTPメッセージをフレームに分割し、インターリーブし、相手側で再構成を行う一連の機能は、HTTP2.0において最も重要な強化ポイント
HTTP/2では1つの接続上にストリームと呼ばれる仮想的な双方向シーケンスを作ること(ストリームの多重化)で問題を克服している。
リクエスト優先度付け
HTTP2.0は優先度の扱いについてのアルゴリズムを指定していない。 ただ、優先度付けが行われたデータをクライアントとサーバ間でやり取りできるメカニズムを提供しているだけ。
クライアントは適切な優先度データを送信すべきで、サーバはその優先度に従って処理と配信を行うべき。
フロー制御
ひとつのストリームがリソースを占有してしまうことで、他のストリームがブロックしてしまうことを防ぐこと
サーバプッシュ
クライアントのリクエスト一つに対してサーバが複数のレスポンスを返すことができる
リソースはドキュメントの構文解析を行うことによって発見されるが、あらかじめサーバからプッシュしてもらう
ヘッダ圧縮
ヘッダのメタデータを圧縮。
効率的なHTTP2.0アップグレードと発見
HTTP2.0のサーバサポートについて情報が存在しない場合、クライアントはHTTPアップグレードを使用して適切なプロトコルのネゴシエートを行う。
このアップグレードを利用して、サーバがHTTP2.0に対応していない場合はHTTP1.1のレスポンスを返します。
まとめ
最高のパフォーマンスのために、「フレーム」という層を新しく設ける そのためにHTTP メッセージをテキスト→バイナリに
その結果 1つの接続上にストリームと呼ばれる仮想的な双方向シーケンスを作ること(ストリームの多重化)でパフォーマンス(レイテンシの改善がされた)が向上。
最後に(まとめと感想)
HTTP Archiveを眺めてると、モダンなwebアプリケーションを見てみるとリクエスト数やファイルサイズの年々増加していることが分かりました。
現在の平均的なWebアプリケーションは約1.8MBのサイズ、約70以上の従属リソースで構成されています。
また、サイトパフォーマンス向上のため多くのWeb開発者が数々の最適化(ファイル結合、スプライト、インライン化)を行っていますが、一時しのぎのためのものであって、本質的な解決策ではないです。
このような背景から、近年のモダンwebアプリケーションにおいて、HTTPのあるべきを考え、HTTP2が開発されたのでは? と読んでて思ったり。
HTTP2を使えば以前は不可能だった多くの最適化が可能になると思っていたのですが、QUICやHTTP/3の使用策定が行われているという話を聞き、 QUICやHTTP/3の話にも興味を持ちました。(これからどうなっていくのだろう...?)
参考
ハイパフォーマンス ブラウザネットワーキング ―ネットワークアプリケーションのためのパフォーマンス最適化
- 作者:Ilya Grigorik
- 発売日: 2014/05/16
- メディア: 大型本