Early Hints と Priority Hints を使用して Webサイトを高速化する方法
Webサイトは依然として読み込みに時間がかかっています。ページの読み込みライフサイクルで最も重要とされる時間に、接続の大半がアイドル状態にあります。Fastly のエンジニアである奥一穂によって提案された新しいテクノロジーでは、この重要な最初の数秒間をより効率的に活用できることが期待されています。
スマートフォンでサイトにアクセスし、テキストが表示されない状態でページを10秒間見つめ続けた経験はありますか?サイトのカスタムフォントが読み込まれるまで真っ白な画面を見つめ続けたい人はいません。そのため、重要なデータをできる限り速く読み込むことができれば合理的です。Link rel=preload は、このような問題の解決策の一端を担うために提案されました。ブラウザは、何らかのコンテンツの読み込みを開始する前に、ページの HTTP ヘッダーを解析します。つまり、必要となることが分かっているアセットのダウンロード開始をブラウザに伝えるには、ここが理想的な場所となります。
デフォルトでは Web は低速
プリロードを使用しない場合、何が起こるのか見てみましょう。ブラウザは、リソースが必要であることに気付いてから、リソースの読み込みを開始します。読み込みが最も速く行われるのは、ドキュメントの最初の解析時に HTML 内にあるリソースです (<script>
、<link rel=stylesheet>
、<img>
など)。
ダウンロードが遅いものは、レンダリングツリーの構築後に確認できます (ここまできて初めてカスタムフォントが必要であることに気付くのですが、これに気付くには、最初にスタイルシートを読み込んで解析し、CSS オブジェクトモデルとレンダリングツリーを構築する必要があります)。
それよりもさらにダウンロードが遅いものは、DOMContentLoaded
などのイベントによってトリガーされる、JavaScript ローダーを介して追加されたリリースです。これらをすべて組み合わせると、最適化されておらず、比較的無意味なウォーターフォールが出来上がります。ネットワークには大量のアイドル時間が発生し、リソースの必要以上に速い読み込み、または読み込みの大幅な遅れのどちらかを引き起こします。
利便性の高い Link rel=preload
ここ数年、Fastly では Link rel=preload を使用し、さらなるサイトの高速化を実現してきました。以下に例を示します。
Link: </resources/fonts/myfont.woff>; rel=preload; as=font; crossorigin
Link: </resources/css/something.css>; rel=preload; as=style
Link: </resources/js/main.js>; rel=preload; as=script
これらを使用することで、ブラウザはヘッダーを受信すると、HTML 本文の解析を開始する前にリソースの読み込みを開始できます。
これは大きな前進です。ページが大きい場合や、これらを使用しなければ見つかるのが遅くなるリソースの場合は特にそうです (フォントがこれに該当しますが、JavaScript アプリケーションをブートストラップするために必要なデータファイルなども重要なリソースになり得ます)。
しかし、さらに改善できる点もあります。ブラウザは、リクエストの送信を完了しレスポンスの最初の1バイトを受信するまでの間、何の処理も行っていません (上図で最初のリクエスト上にある大きな緑色のセクション)。
Early Hints を使用して「サーバーの思考」時間を利用する
一方、サーバーは非常にせわしなく稼働しています。レスポンスを生成し、それが成功したかどうかを確認しています。データベースへのアクセス、API コール、認証などを経ると、ユーザー正しいレスポンスが 404 エラーだと判断してしまうかもしれません。
サーバーの思考時間は、ネットワークレイテンシより短い場合もあれば、大幅に長い場合もあります。重要なことは、これらは一致するわけではないということです。サーバーが思考している間、私たちはクライアントに一切データを送信していません。
しかし、興味深いことに、レスポンスの生成について考え始める前であっても、ページをレンダリングするために読み込む必要があるスタイルフォントの一部はすでに分かっているはずです。結局のところ、一般的に、エラーページには通常のページと同じブランディングやデザインが使用されています。サーバーが思考を始める前にこれらの Link: rel=preload
ヘッダーを送信できるのが理想的です。これこそ、Fastly で私の同僚である奥一穂が制作し、HTTP ワーキンググループの成果として RFC8297 に掲載されている、Early Hints の制作目的です。単一のレスポンス内の複数のステータス行による魔法をご覧ください。undefined
HTTP/1.1 103 Early Hints
Link: <some-font-face.woff2>; rel="preload"; as="font"; crossorigin
Link: <main-styles.css>; rel="preload"; as="style"
HTTP/1.1 404 Not Found
Date: Fri, 26 May 2017 10:02:11 GMT
Content-Length: 1,234
Content-Type: text/html; charset=utf-8
Link: <some-font-face.woff2>; rel="preload"; as="font"; crossorigin
Link: <main-styles.css>; rel="preload"; as="style"
サーバーは、リクエストを受信するとすぐに、「情報」レスポンスステータスと言われる最初の部分を書き込み、ネットワークにフラッシュします。これにより、実際のレスポンス内容を判断し、レスポンスの生成に取りかかることができます。その間ブラウザでは、極めて早い段階からプリロードを開始できます。
これを行うには、ブラウザ、サーバー、CDN が機能する形での何らかの変更が必要になりますが、一部のブラウザベンダーは、実装の難しさに懸念を表しています。そのため、これがいつデプロイされるかは依然として不明です。この進捗状況は、Chrome や Firefox のバグ追跡トラッカーでご確認いただけます。
最終的には、皆さんが Early Hints を Fastly から直接出力しながら、引き続きリクエストをオリジンサーバーに送信できるようになることを願っています。これをどのように VCL で公開するかはまだ決定していませんので、ご意見があれば是非お聞かせください!
HTTP/2 サーバープッシュについて
HTTP/2 では、サーバープッシュと呼ばれる新しいテクノロジーを利用できます。これも、Early Hints レスポンスで Link rel=preload を送信するのと同じ問題を解決するように見えます。プッシュは効果ではありますが (Fastly ではエッジからカスタムプッシュを生成することも可能)、いくつかの理由から数多くの反対意見も挙がっています。
サーバーは、クライアントがすでにリソースを持っているかどうかを知らないため、必要がない場合でも、プッシュを行います。ネットワークバッファリングやレイテンシの関係上、クライアントには通常、コンテンツ全体を受信する前にプッシュをキャンセルする機会がありません (ただし、問題への解決策は、Akamai の Yoav Weiss 氏と奥一穂が共同で取り組んでおり、キャッシュダイジェストのヘッダー案という形で、存在しています)。
プッシュされたリソースは接続によって束縛されるため、クライアントが結局は使用しなかったリソースを簡単にプッシュできます。なぜなら、クライアントは別の接続を介してリソースのフェッチを試みるからです。同じ TLS 証明書を共有していない別のオリジンサーバー上にリソースがある、またはリソースが異なる認証情報モードでフェッチされているため、クライアントは別の接続を使用する必要がある場合があります。
H2 プッシュは、主要なブラウザベンダーを比較すると、あまり一貫性を保って実装されていません。つまり、特定のユースケースで機能するかどうかを予測するのは困難です。
いずれにせよ、Early Hints とサーバープッシュには、それぞれ違ったメリットとデメリットがあります。Early Hints は、追加可能なラウンドトリップと引き換えに、ネットワークのより効率的な運用を実現します。レイテンシが低くサーバーの思考時間が長いと予測される場合は、間違いなく Early Hints が適切な解決策です。
しかし、これは全てに当てはまるとは限りません。肯定的に捉えてみましょう。人類が近いうちに火星に住むことになると想像してみたとします。火星の住民は、ラウンドトリップごとに20 - 45分間のレイテンシを経験しながら Web を閲覧することになります。このため、追加のラウンドトリップには相当な苦痛が伴い、サーバーの思考時間はこれに比べれば取るに足らないものとなります。この場合は、サーバープッシュに軍配が上がります。しかし、火星から Web を閲覧するのであれば、現在の Web パッケージや Signed Exchange などの発案から想像されるような、ある種のパッケージされたバンドルをダウンロードする可能性が高くなります。
副次的なメリット : より高速なリクエスト共有
Early Hints の主なユースケースはブラウザ内にありますが、コンテンツ配信ネットワーク (CDN) にも興味深い潜在的メリットがあります。Fastly では、同じリソースに対し大量のリクエストが同時に受信された場合、通常、これらのうち1つのみをオリジンサーバーに送信し、その他は待機キュー内に入れます。これはリクエスト共有と呼ばれるプロセスです。オリジンサーバーから最終的に私たちに返ってくるレスポンスに Cache-Control: private
が含まれている場合、キューに入っているリクエストをすべて出し、これらをすべて個別にオリジンサーバーに送信する必要があります。なぜなら、このひとつのレスポンスを使用して複数のリクエストに対応することが許可されていないからです。
Fastly では、最初のリクエストに対するレスポンスを受信するまではこの決定を下すことができません。しかし、Early Hints がサポートされるようになり、サーバーが Cache-Control ヘッダーを含んだ Early Hints のレスポンスを発行する場合、キューを単一のリクエストで共有できないことをより早く知ることができ、代わりに、キューに入っているすべてのリクエストをオリジンサーバーに即時送信できる可能性があります。
Priority Hints を使用して重要性の低いコンテンツに順序を付ける
Early Hints は、ウォーターフォール上で最も価値のある空き容量にアクセスする優れた方法です。ネットワークがアイドル状態の場合、ユーザーは待機中にあり、送信中のリクエストは1つしかなく、画面には何も表示されません。しかし HTML が読み込まれ、ページの解析が始まると、フェッチを必要とする大量のリソースが急激に増加します。ここで重要なのは、データをできる限り早く読み込むことではなく、正しい順序で読み込むことです。ブラウザは、気が遠くなるほど複雑なヒューリスティックの配列を使用して、ブラウザ自体で読み込みの優先度を決定します。しかし、その判定を無効にしたい場合、将来的には Priority Hints を使用して無効化できるようになる可能性があります。
<script src="main.js" async importance="high"></script>
<script src="non-critical-1.js" async importance="low"></script>
開発者はこの新しい重要な属性を使用して、ダウンロードが殺到している場合には、リソースがダウンロードされる順序をコントロールできます。また、CPU とネットワークがアイドル状態になるまで、またはデバイスの選択に関するその他の理由により、ブラウザが重要性の低いリソースを遅らせることも可能です。
使用に関して
現在、Priority Hints や Early Hints はサービスとして提供されていません。Fastly が使用およびサポートしている HTTP/2 サーバーの H2O は、Early Hints に対するサポートを開始しています (PR 1727 および 1767 を参照)。また、Chrome に Priority Hints を実装したり、導入時にデータを提供するための Chrome チームによる 1xx レスポンスのアクティブトラッキングを実装したりする方針もあります。その間、Early Hints の提供を開始しても何ら問題はないでしょう。先手を打つならば、是非一歩を踏み出しましょう!