この記事は Nikkei Advent Calendar 2020 20日目の記事です。
日経電子版Webチームの阿部です。今回はHTTP/2の目玉機能の1つであったServer Pushの事実上の終焉と、現在ChromeチームやFastly社が実験中の103 Early Hintsについて、日経電子版Webでの取り組みを紹介させていただきます。
HTTP/2 Server Push
HTTP/2 Server PushはHTTP/2で策定された、一言で言ってしまうと「必要なリソースがリクエストされる前にサーバーからクライアントに送ってしまおう」という技術です。
これによりクライアントがリクエストするリソースを先回りしてサーバーが送ることで、必要なリソースが揃うまでにかかる時間を短縮できるため、パフォーマンスの向上が期待されていました。
Server Pushの問題点
一方、サーバーからクライアントにリソースを送っても、クライアントがすでにそのリソースをキャッシュで持っている場合は無駄にリソースを送ってしまうことになります。

ChromeがServer Pushのサポートを終了
前述のような背景もあってか、Chromiumの開発チームによるとServer Pushは未だ普及していなく、また使いこなすのも非常に難しくパフォーマンスの向上を期待できるものではないということで、 ChromeからHTTP/2およびgQUICのServer Pushを取り除く告知がされました。(Intent to Remove: HTTP/2 and gQUIC server push)
一部抜粋すると、具体的な数値としてログやサーベイの結果が書かれています。
Over the past 28 days, 99.95% of HTTP/2 connections created by Chrome never received a pushed stream (Net.SpdyStreamsPushedPerSession), and 99.97% of connections never received a pushed stream that got matched with a request (Net.SpdyStreamsPushedAndClaimedPerSession).
(過去28日間、ChromeのHTTP/2コネクションの99.95%はServer Pushのストリームを受信していなく、コネクションの99.97%リクエストとマッチしたプッシュストリームを受信したことがなかった)
In June 2018, 99.96% of HTTP/2 connections never received a pushed stream.
(2018年6月時点で、HTTP/2コネクションの99.96%がプッシュストリームを受信したことがなかった)
less than 40% of received pushes are used, down from 63.51% two years ago.
(受信したプッシュストリームのうち、実際に使われているものは40%だった。これは2年前の63.51%よりも低下している)
他にもAkamaiによる研究結果などにも触れられています。興味のある方はIntentsの本文やコメント欄を読んでみてください。
103 Early Hints
Server Pushとは別の方法で、クライアントがいち早くリソースを取得する方法として提案されているのがHTTP status codeの一種である103 Early Hintsです。
Early HintsはHTTP Informational Responseの1つで、status codeは103が割り振られています。
サーバーは受信したHTTPリクエストに対し、最終的なレスポンスを送信するよりも前に、status code 103でレスポンスを送信することができます。
このレスポンスにはbodyは含まれず、サーバーは最終的なレスポンスに含まれる可能性が高いヘッダーフィールドをクライアントに示すことだけができます。
クライアントはEarly Hintsを受信すると、ヘッダーフィールドの値を評価し、何らかの処理(リソースのfetchなど)を行うことができます。 ただし、最終的なレスポンスの処理に対して、Early Hintsで送信されるヘッダーフィールドの評価がパフォーマンス最適化以外の影響を与えることは禁止されています。
ここではRFCでの例示や現実的なユースケースに沿って、linkヘッダーをEarly Hintsに含めたリクエスト/レスポンスのやり取りの例を示します。

上の画像の例では、
- クライアントがサーバーに
/index.html
をリクエストする - サーバーはクライアントにstatus code 103で、
link
ヘッダーを含めたレスポンスを送信する - サーバーは、リクエストを受け取ってから200msかけてHTMLを生成し、リクエストに対する最終的なレスポンスとしてクライアントに送信する
というやりとりが起きています。
ここでクライアントが103を無視する場合と、103を理解し即座にlinkヘッダーで示されるリソースを取得しようとする場合を考えてみます。
下に例示する、Early Hintsをサポートしていないクライアントは、103レスポンスを無視し、GET /index.html
に対するレスポンスを受け取ります。
レスポンスヘッダーにはlink: </main.css>; rel=preload; as=style
があり、このクライアントは /main.css
をサーバーに対しリクエストします。
ここで/main.css
をサーバーが送信するまでに20msかかるとすると、通信経路でかかる時間を無視すると、HTMLとCSSを揃えるのに最短220msかかることがわかります。

一方、次に例示するクライアントはEarly Hintsを理解し、Early Hintsのlink
ヘッダーに書かれているリソースを即座にリクエストします。
これによりクライアントはGET /index.html
のレスポンスを待たずにGET /main.css
をリクエストを送信することが可能となり、先の例でリソースを揃えるのに最短220msかかっていたものを200msに短縮することができます。

/main.css
のキャッシュを持っている場合は、リクエストを送るかキャッシュを利用するかはクライアントが判断することができ、Server Pushで起きていた不要なトラフィックの使用は発生しません。

FastlyとChromeの効果計測実験
FastlyとChromeチームは協力して、このEarly Hintsが実際にどの程度リソースの取得時間短縮に役に立つかの効果計測を開始しています。
- https://chromium.googlesource.com/chromium/src/+/master/docs/early-hints.md
- https://www.fastly.com/blog/beyond-server-push-experimenting-with-the-103-early-hints-status-code
この記事執筆時点で最新のStableバージョンであるChrome87では、前述したようなEarly Hintsを解釈し即座にリクエストを送る実装は入っていません。
リクエストを送りませんが、Early Hintsを受け取ったタイミングとEarly Hintsのlink
ヘッダーに記載されたリソースをリクエストした時間などから、指標となるデータを記録しています。
このような効果計測には多くのデータが必要なため、ChromeチームとFastlyはこの実験への参加を募っています。
なお、ここでChromeにデータが送られるのは、ユーザーがChrome使用時にusage staticsとcrash reportsの送信に同意している場合のみとなっています。(https://chromium.googlesource.com/chromium/src/+/master/docs/early-hints.md#how-to-contribute-to-the-measurement)
日経電子版Webでの利点
日経電子版Webは内部的には複数のサービスを持っていて、例えば記事ページは記事ページ用のサービス、トップページはトップページ用のサービス、と完全に別れている構成になっています。(参考)
ここで記事ページを例に取ると、日経電子版Webで記事を開くとURLはhttps://www.nikkei.com/article/EXAMPLEARTICLE
という形式になっていて、Fastlyを経由して記事ページ用のサービスにリクエストが送られていることになります。
記事ページにはどの記事ページでも使われる共通のJS, CSS, 画像がありますが、クライアントがこれらのサブリソースを取得するのはhttps://www.nikkei.com/article/EXAMPLEARTICLE
からHTML(ここではサブリソースと対比してメインリソースと呼びます)をレスポンスとして受け取った後になります。
メインリソースのキャッシュがFastly上にあれば、非常に速くレスポンスが返ってくるのでこれでもパフォーマンス上の問題は小さいですが、Fastlyでキャッシュヒットせずにオリジンまで到達してしまうと、 メインリソースのレスポンスを受け取るまでにそれなりの時間を要してしまい、必要なサブリソースを取得するタイミングも遅れてしまいます。
メインリソースへのリクエストがあった際に、Fastlyのエッジで絶対に必要なサブリソースをEarly Hintsでクライアントに伝えることで、キャッシュヒットしたか否かに関係なく、クライアントはメインリソースのレスポンスを待たずにサブリソースの取得を開始することができます。
FastlyのエッジからクライアントにEarly Hintsを送る
Fastlyを使っていれば、Early Hintsをクライアントに送ること自体は簡単です。
前述したFastlyのブログ内に記述されているh2.early_hints
関数を用いて、クライアントにEarly Hintsを送ることができます。
今回は例として、日経電子版ページ最上部にあるロゴ画像をEarly Hintsのlink
ヘッダーに含めてみます。
h2.early_hints("link: </.resources/k-components/logo/masthead.rev-b8cf30e.png>; rel=preload");
curlでリクエストを送り、103が送られてきているかを確認することができます。
curl 'https://www.nikkei.com/article/DGXZQODZ142XU0U0A211C2000000' -LIXGET
HTTP/2 103
link: </.resources/k-components/logo/masthead.rev-b8cf30e.png>; rel=preload
HTTP/2 200
content-type: text/html; charset=UTF-8
content-encoding: gzip
accept-ranges: bytes
date: Fri, 18 Dec 2020 02:38:47 GMT
set-cookie: ...
cache-control: no-cache,must-revalidate,proxy-revalidate
referrer-policy: same-origin
vary: accept-encoding,Accept-Encoding
content-length: 45324
残る課題
上の例で示した例では、ロゴ画像をEarly Hintsでクライアントに伝えていますが、これはほとんど更新されず、ファイル名についているrevisionが変わることも稀なリソースです。 こういったリソースはクライアントでのキャッシュが効く可能性が高いのですが、実際にEarly Hintsで送りたいリソースは、アプリケーションの変更でrevisionが頻繁に更新され、さらにCritical rendering pathsに影響しうるJSやCSSといった静的なファイルです。
しかし、これらのリソースをエッジがリクエストを受け取り、オリジンとリクエスト/レスポンスのやりとりを行う前にクライアントに伝えるには、リソースのURLを予めエッジで動くVCLが静的に知っている必要があります。
つまり、アプリケーションの新しいバージョンをビルドしたらVCLも新しくFastlyにデプロイしなければいけないということになります。
解決策としては、Fastlyの持つDictionariesという、VCLのランタイムから参照できAPIで更新できる辞書機能に、 各サービスと必須なサブリソースの対応表を書き込んでいくという方法を考えています。
何かアップデートがあれば、追記もしくは新しい記事を投稿しようと思います。
関連文献
- "An HTTP Status Code for Indicating Hints" https://httpwg.org/specs/rfc8297.html
- "Beyond Server Push: experimenting with the 103 Early Hints Status Code | Fastly" https://www.fastly.com/blog/beyond-server-push-experimenting-with-the-103-early-hints-status-code
- "ChromeのHTTP/2サーバプッシュサポート廃止検討と、103 Early Hintsについて - ASnoKaze blog" https://asnokaze.hatenablog.com/entry/2020/11/13/001110