本セッションの登壇者
セッション動画
奥一穂と申します。今日は「これからのHTTP~拡大するセマンティクスと高速化」と題して話をさせていただきます。
私はFastlyというCDNの会社で、シニアプリンシパルOSSエンジニアという仕事をやっています。具体的には、Fastlyで使われているHTTPのターミネーターサーバー実装であるH2Oの開発と、その中で使われているTLSやQUICのスタックのメンテナーもやっています。その他、標準化関連ではいくつかのRFCやドラフトで著者や共著者を務めています。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672688/entries/mpsujryosgyzfmkqbgum.png)
今日は2つの話題、拡大するセマンティクスの話とHTTPの高速化についてお話ししようと思います。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672693/entries/ipttw2mre4wzfolihzis.png)
HTTPセマンティクスの進化とバージョン非依存
第1の話題で、HTTPセマンティクスについてです。先ほどのセッションにもありましたが、2022年にHTTPの機能と通信仕様を分離しました。機能としてリクエストやレスポンスがあって、リクエストにはメソッドとURIとヘッダが含まれる、レスポンスはステータスコードとヘッダが含まれる、それぞれボディがつくこともある、キャッシュのタイミング規定などがHTTPのセマンティクスです。それに対して、具体的にどうエンコードしてTCPやQUICの上で流すかを定めているのがHTTPの各バージョン、HTTP/1.1、/2、/3といった通信仕様です。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672709/entries/lln7tcpi84dw7mp3ct2b.png)
図式化すると、HTTPには、メソッド、URI、ステータスコード、ヘッダと、ボディがあり、HTTPアプリケーションがHTTPメッセージでこれを操るというレイヤー構造になっています。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672716/entries/cgka41jkistlpbzkxyu4.png)
それに対して、リクエスト&レスポンスではないHTTPというのもあります。これが「CONNECTメソッド」と呼ばれるものです。使い方は、たとえばプロキシを介してHTTPSを処理する際に、プロキシに接続先サーバーの情報を入れたCONNECTメソッドを投げます。CONNECTメソッドで作った接続は双方向バイトストリームになってTCPを中継してくれるので、その上でTLSを流します。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672722/entries/zv024rax81berbelwbdo.png)
中身はこのようになっています。通常のHTTPと違うところは、ボディを交換する代わりに双方向バイトストリームを作ってデータを送受信する点です。この双方向バイトストリームを使うのは、CONNECTだけではありません。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672727/entries/k8acckz7aweavxbpcdyn.png)
2011年に出てきたWebSocketはHTTP/1.1をupgradeという形で双方向ストリームに変えて、その上でWebSocketフレームというメッセージ的なものを交換するための仕組みです。WebSocketはもともとHTTP/1用でしたが、HTTP/2とHTTP/3についても仕様を定めたので使えるようになりました。/2や/3にはupgradeという仕組みがないので、代わりにextended CONNECTという仕組みを作って、リクエスト&レスポンスではなく双方向ストリームでバイト列をお互い自由に送り合える仕組みを作っています。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672735/entries/wyooww5i1m6agsz9agfc.png)
具体的にレイヤーで書いてみるとこんな感じです。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672741/entries/m6dtll9eivsr5t7nx5ke.png)
次に出てきたのが、HTTPをパケットのトンネルにしたいというものです。具体的には2022年に標準化された、UDPをHTTPの上で流すためのmasque(マスク)という仕組みです。そして今、IPパケットやEthernetフレームをトンネルするものが議論されています。VPNを作るのに使います。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672749/entries/gbfasgigajwuwntqj30x.png)
これを実現するために、「HTTP Datagrams and Capsule Protocol」というものが定義されています。HTTP/3はQUICベースで、QUICはUDPだから暗号化されたデータを投げることができるんですね。でもHTTP/2や /1はUDPでなくTCP上なので、データを投げる機能がありません。その場合は、双方向バイトストリームの上で代わりにCapsuleというコンテナフォーマットを作って、それを利用することにしました。これによってHTTPのバージョンに関係なく、データパケットを投げられるようになったというのがmasqueです。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672758/entries/gw18ufyur8xrsyl74zjt.png)
構造を図に示します。メソッド、URI、ステータスコード、ヘッダは変わらないけれども、QUICのデータグラムの上でパケットを投げることも、双方向バイトストリームの上でCapsuleという抽象化を使った上でパケットを投げることもできるというのがmasqueです。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672765/entries/dftbieuoylqeggmhhnzc.png)
さらに、HTTPを多重ストリーム化しようというのが、WebTransportという仕様で、現在標準化が進んでいます。今までと違うのは多重ストリームという点です。HTTP/3ではQUICのストリーミング群に直接マッピングしますが、HTTP/2以前では1本のストリームしかないので、Capsuleを利用してQUICストリーム群と同様の機能を実現するという形になっています。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672771/entries/zfa5dfj2fch6zlh9jlfr.png)
WebTransportのレイヤーはこのようになっています。メソッド、URI、ステータスコード、ヘッダはそのままで、双方向バイトストリームの上でCapsuleを使って、多重化した双方向バイトストリームを実現するのがHTTP/2の場合です。HTTP/3の場合はQUIC streamsをそのまま使います。これにより、多重化された双方向バイトストリームでデータを送受信します。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672778/entries/uzkhunvicml5wntusaj1.png)
これらがセマンティクスとHTTPバージョン非依存でみんながやろうとしていることです。セマンティクスをバージョン非依存で定義したのはよかったと思います。一方、HTTPがあちこちで使えて運用性が良いがために、その上で何でも流したいという時代になっていると言えるかもしれません。苦労する点としては、HTTPのバージョン間での変換が必要になることがあります。たとえば、クライアントとプロキシの間はHTTP/3だけれども、プロキシとバックエンドサーバーの間がHTTP/2だったら、QUIC streamを使ったストリームをCapsuleを使うストリームに変換してやる必要があります。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672784/entries/pulrbs1xqwomu2ss0axb.png)
このため、別解の提案もされています。私も共著になっていますが、QUICをTCP(TLS)の上に移植しようということです。そうすると、どこでもHTTP/3を使えるようになるのでHTTPのバージョン間の差や変換を気にしなくてよくなります。ここまでが、拡大するセマンティクスに関する現時点でのまとめです。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672794/entries/nzns15mbp40etuycsgpz.png)
さらなる高速化:バンド幅の有効活用からバンド幅の拡大へ
では次の話、さらなる高速化です。まず、HTTPの高速化の歴史を振り返ってみましょう。
HTTP/1.1が1997年に出て、複数TCP接続ができるようになりました。HTTP/1.0はリクエストを1個を投げるごとに接続を切っていたので時間がかかっていましたが、1.1では複数のリクエストができるようになって高速化されています。また、HTTP/2が2015年に出て、1本の接続で同時に複数のリクエストを投げられるようになりました。そのために優先度制御やサーバープッシュの機能が入りました。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672806/entries/fgrwnjhrmaskdjdqojga.png)
2017年にTLS/1.3が出て、TLSハンドシェイクの遅延削減が行われます。TLSのハンドシェイクは従来は2ラウンドトリップ(RT)、つまりクライアント→サーバー→クライアントを2往復していたのが、1往復でできるようになりました。再接続の場合には往復を省いてリクエストを投げられるようになったのがTLS/1.3です。そして、2022年にHTTP/3が出ました。従来はTCPのSYN/ACKで1 RTを費やしてからTLSのハンドシェイクをやっていたのに対して、QUICではTLSのハンドシェイクとトランスポートのハンドシェイクを同時にやるのでこれを削減できます。また再送制御を高度化し、新しい優先度制御の仕組みを導入しました。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672812/entries/rkkevsxkthbzwmkmoivi.png)
さらに、サーバープッシュがうまくいかず、使われなくなったこともあって、最近はEarly Hintsというのが利用されるようになってきています。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672817/entries/pvnijj9s8wuprbcdakqx.png)
プロトコルレベルの高速化で重要になってくるのは、HTTPのメインはウェブブラウジングなので、クライアントが接続を確立してからレンダリングまでの時間をできるだけ短くすることです。そのために我々が何をやってきたかというと、ハンドシェイクの短縮です。従来のTLS over TCPでTLS 1.2の場合は3 RTかかっていたのが、HTTP/3で1 RTでできるようになりました。再接続の場合はいきなりつなげることができるので0 RTです。
また、バンド幅の有効活用というのをやってきました。この場合、複数のHTTPレスポンスを多重化して投げるので、優先度制御が必要になります。たとえばダイナミックに生成するページだと、PHPで計算したり、データベースに問い合わせたりと時間がかかりますし、CDNならさらにオリジンまで問い合わせる必要があります。そこで、先にCSSを投げられるようにHTTP/2の優先度制御を作り直してExtensive Prioritiesにしました。また、HTMLの生成時間の間にCSSを投げるというのはサーバープッシュの用途としてもともと考えられていたことですが、サーバープッシュでうまくいかなかったので、代わりにEarly Hintsを使用するのが最近の流れになってきています。このように、バンド幅の有効活用についてはほぼできることをやってきた感じです。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672825/entries/aqpycgnpeub6c3obmtn1.png)
バンド幅の有効活用をやり尽くした後は、バンド幅を広げることになります。ハンドシェイク直後のバンド幅を増やす、具体的には接続確立直後というのは輻輳制御の中でもスロースタートと呼ばれる機能が働く部分になっているんですが、これを何とかできないかということです。輻輳制御で最初の速度を決めるスロースタートというフェーズでは、最初にInitial Windowといって一定数のパケットを投げます。たとえば10個のパケットを投げてAckを受け取ったら、次は20個、次は40個と倍々で増やしていきます。もしパケットロスが発生したら輻輳回避モードという穏やかなモードに切り替えますが、最初はこのスロースタートで始まります。Initial Windowのサイズは、RFCでは10パケットを推奨していますが、現実には30パケットぐらいでやっているところが多いです。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672832/entries/zdqo9sugvls4qpwmzv3b.png)
スロースタートとCareful Resumeの数値上の効果
では、実際にスロースタートの影響はどれぐらいあるのでしょうか。日本国内の5G回線を見てみましょう。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672844/entries/lmcsgn28isl4n1c5t9d5.png)
これは自分の例ですが、ベンチマークのサイトを使うと55 Mbps出ます。Unloaded、つまりアイドル状態で39 ms、負荷を100%かけているLoadedの状態が194 msです。これは実際に何を意味しているのでしょうか。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672851/entries/sdfbmxva6zjud1ujorsm.png)
図に示すように、まず、アイドル時容量というのは、ボトルネックをパケットが通過できる速度とそれにかかる遅延です。PCの手前にある一番細いところの前にパケットがいっぱい積み上がっています。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672858/entries/jhviva8t8ovzzxysbykq.png)
ここからアイドル時容量がどれぐらいあるかを計算します。{1秒あたり55Mb}×{1往復39 ms}=2.15 Mb、つまり268 KB。これをパケットサイズで割ると、209パケットのアイドル時容量があるということになります。
ネットワークを飽和させる100%を使おうと思うと、最低1RTごとに209パケット投げてやる必要があることになるわけです。でも、スロースタートで最初は30パケットなので、最初の1RTでは30にしかなりません。次で60、次で120、4回目でやっと240ということで、無駄がある。一気に209投げるのが本当は最適なのですが、バンド幅は分からないので保守的にやっていて、時間がかかるわけです。これを何とかしたいです。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672867/entries/yt45zwrw1zawly3r4yne.png)
では、シミュレーションを見てみましょう。さっきの回線状況をシミュレーターで作って、パケット送信とAck受信のタイミングを測定してみました。40 msぐらいで最初のパケット(黒)が出ていって、次の80 msぐらいでまたパケットが出ていくと同時にAck(黄色)が返ってきます。送信側の速度が回線速度よりも速くなってくるので、パケットがキューに積み上がって遅延が出ます。黄色の遅延と黒のパケット送信のタイミング差が広がっていくというのが、このシミュレーションが表しているものです。
![](https://res.cloudinary.com/techfeed/image/upload/v1717673396/entries/frwttbivqaeilixsgohq.png)
スロースタートの速度が平均して上がらない問題を直そうというのが、「Careful Resume」という仕様で、IETFのワーキンググループで標準化途上になっています。
これは、前回接続時の回線容量を覚えておいて、再接続時はいきなりその速度で投げるという仕組みです。パケットロスが発生したら予測失敗ということで小さなウィンドウでやり直し、発生しなければウィンドウサイズはそのままでスタートを継続するというのが、仕様の概要です。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672899/entries/m6ajwgllvvj6abk2cme0.png)
実際にH2Oで実装してもうマージされています。ただし、細かな設計はドラフト仕様とは差異があります。
![](https://res.cloudinary.com/techfeed/image/upload/v1717673446/entries/awpbjbsagwnz9vgkebmd.png)
これが「Careful Resume」のシミュレーション結果です。スロースタートでは黒の送信側がグループごとに分かれていましたが、連続しています。また黄色の応答もホッケースティックのようにだんだんと上がっていたのが、ほぼ一直線に上がっているのが分かると思います。
![](https://res.cloudinary.com/techfeed/image/upload/v1717673044/entries/yxhemohjlojwijbgc18e.png)
実際に1 MBを投げてAckの返る時間を比べてみると、スロースタートの場合は330 msかかっていたのが、Careful Resumeだと270 msということで、ダウンロードが1 MBあたり60 ms早くなる効果が出ています。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672909/entries/tdmabvarr0kdsqttgq7w.png)
まとめます。HTTPの高速化に関しては先ほど言ったようにHTTP層トランスポート層を最適化済みなので、今は輻輳制御のチューニングも実装の対象になってきているというのが最近の動向かと思います。
![](https://res.cloudinary.com/techfeed/image/upload/v1717672946/entries/xhoq44iyexzzxa5a2boe.png)
ということで、私の方からは以上2点、セマンティクスが拡大しているよという話と、HTTPの高速化で最近何をやっているのかを紹介させていただきました。ありがとうございました。