この記事で伝えたいこと
HTTP/2 で HTTP レイヤの Head of Line Blocking 問題が解決した他、Server Push、Prioritization、HPack などの導入によって、HTTP の通信効率を向上させました。
しかし、TCP を使う限り、TCP レイヤの HOL Blocking は依然として残っています。
更なるパフォーマンス向上を目指して、QUIC では UDP を使って通信します。UDP 自体はパケットの欠損検出や再送処理など、TCP では当たり前にある機構が無く、自前で作り上げないといけません。では、QUIC は TCP と同等の機能を実現しながらも、TCP とどこが異なるのかを探っていきます。
ちなみに、QUIC という名前は、Quick UDP Internet Connection の略です。
復習:HOL Blocking とは?
ウェブブラウザで HTTP/1.1 以前のプロトコルを使うとき、同時に送信できるリクエストの数に上限があります。その上限数より多くリクエストを送信したい場合は、先に出したリクエスト処理が終了するまで待たなければなりません。これは、HTTP レイヤにおける Head of Line Blocking (HOL Blocking) と呼ばれています。
HTTP/2 では、TCP のペイロードを仮想的なストリームに分割することで、1 本の TCP 接続で複数のリクエスト/レスポンスを扱えるようになりました。これにより、先に送信したリクエスト処理の完了を待たずに次のリクエストを送信できるようになり、HTTP レイヤでの HOL Blocking 問題が解決しました。
しかしながら、HTTP/2 は TCP を前提としています。このため、途中でパケットが欠損したとき、再送信されたパケットを受信するまでアプリケーションはそれ以降のデータを受け取ることができず、処理を待たなければいけません。これを TCP の HOL Blocking といいます。もし TCP の HOL Blocking 問題が解決できれば、HTTP の通信をよりハイパフォーマンスなものにできると期待できます。
QUIC の基本
順序は Stream ごとに揃っていればよい
TCP でデータを受信すると、OS でデータの順序を揃え終えた後にアプリケーションにデータを渡します。
一方、QUIC では全てのデータを順番通りに揃える必要はなく、「各 Stream 内のデータ順序さえ揃っていればよい」という考えが土台になっています。先にデータを受信し終えた Stream から処理をディスパッチすることができるため、パケットロスによる処理待ちを最小限に抑え込むことができます。
複数の HTTP リクエストを、順番通りに受け取って欲しいときはどうするの?
Stream は複数の Stream Frame から構成されます。複数の HTTP リクエストを意図した順序でアプリケーションに届けるためには、同じ Stream 内にリクエストを並べることで対応できます。各 Frame のデータ開始位置をまとめて指定するフィールドが Stream Offset です。
TCP の課題をどう解決しているか?
今回は、2 つをピックアップして紹介します。
1. 「送信したパケットの順序」と「アプリケーションが受け取るべき順序」を分離
- 送信したパケットの順序を Packet Number と呼び、再送時であっても必ずインクリメントする
- アプリケーションに届けたい順序は、同一 Stream 内の Stream Frame の並び順で表現する
これは、TCP で問題になっていた retransmission ambiguity (*1) を解決するためのルールです。このルールのおかげで、送信側は QUIC の Packet Number を使って TCP よりも確実に Fast Retransmit Recovery が利用できようになる上、RTT を正確に把握できるようになります。
復習:(*1) retransmission ambiguity とは?
パケットを送信した後、ACK が返ってこないので、同じパケットを再送したとします。再送後に ACK を受信したとき、この ACK は、「最初に送ったパケットに対する ACK」なのか、「再送パケットに対する ACK」なのか区別できません。
これを retransmission ambiguity といい、TCP では 3 個のパケットが連続して ACK Timeout するまで待つなどしないと効果的な congestion control ができませんでした。
QUIC は再送時にも Packet Number をインクリメントするおかげで、両者を区別できます。
2. Reneging 禁止
QUIC の ACK は TCP の SACK (Selective ACK) と同等の情報を持っています。TCP と異なるのは、ACK が返ってきたパケットを後から破棄 (renege) することを禁止した点です。
これによって、送信側と受信側両方の実装を劇的にシンプルにできる上、送信側のメモリ消費を低減できます。
ちなみに、TCP の SACK は 3 個のパケットの ACK 情報までしか格納できません。QUIC では 256 個まで持てるため、ロス率の多い環境でより効率よく Fast Retransmit できます。
まとめ
QUIC は UDP の上で TCP + TLS + HTTP/2 相当の機能を盛り込んでおり、実装が超絶難しいプロトコルです。RFC draft を読み進めていくと、日常生活でよく使われている TCP にどのような課題があるのかが分かり、とても面白いと思います。
参考
https://tools.ietf.org/html/draft-ietf-quic-recovery-00
https://tools.ietf.org/html/draft-hamilton-early-deployment-quic-00