Posted at
HTTP2Day 13

HTTP2 のプライオリティ制御

More than 3 years have passed since last update.


余談

余談ですが, 現在の仕様では "HTTP2.0" ではなく "HTTP/2" もしくは "HTTP2" が正しい名称です.


プライオリティ制御

この記事は HTTP2 Advent Calendar の 13 日目の記事です。

初回は、執筆時点での最新ドラフトである HTTP2-draft16 のプライオリティ制御(Priority Control) について解説します。


プライオリティとは

HTTP2 における Priority = 優先度の制御とは、 Stream に対して優先度をつけることによって、ブラウザなどが先に欲しいリソースを、サーバに先に通知する仕組みです。

以下のような制限があります。


  • プライオリティはクライアントがサーバに送る

  • サーバは優先度を無視しても構わない(必須ではない)

  • 優先度は配分であり、実際にはフロー制御によるリソース消費の割合を指定するようなイメージ。

  • 優先度が高い Stream が低い Stream を完全にブロックするような仕様ではない。

  • 優先度には依存がある。

フロー制御については、以下を参照ください。

http://qiita.com/Jxck_/items/622162ad8bcb69fa043d


プライオリティの例

例えばクライアントがサーバに index.html をリクエストし、 index.html には以下の JS/CSS/Image がリンクされているとします。

<!-- index.html -->

<script src="script.js"></script>
<link rel="stylesheet" href="style.css">
<img src="image1.png">
<img src="image2.png">

この場合、ブラウザであればいち早くページの見た目を完成させて、ユーザに表示することが優先されます。この場合コンテンツを優先度の高い順で並べると概ね以下のようになるでしょう。(画像はサイズが大きいため、 CSS などで表示領域を確保し、実際の画像データはあとから取ります)


  1. index.html

  2. style.css

  3. script.js

  4. image1.png

  5. image2.png

これら 5 つのコンテンツは、全て同一コネクション上の別のストリームで転送されます。

前回のエントリで解説した通り、コネクションレベルのフロー制御は、コネクション全体の Window Size をストリームが取り合い、ストリームの Window Size に余裕があっても、コネクションの Window Size が無ければ、 DATA Frame を送信することができません。

このコネクションレベルの Window Size というリソースを、ストリームに分配するヒントとして、 Priority の値が使われるということです。


ストリームの重み(Weight)

HEADERS フレームでストリームを開始した場合、このフレームの Weight に指定した値が、ストリームの重みになります。

Weight は 8 bit なので 0~256 までの値が指定され、省略可能であり、デフォルトは 16 です。

例えば、以下のように重みが指定された場合を考えます。


  1. image1.png (w=32)

  2. image2.png (w=16)

  3. image3.png (w= 8)

この場合、三つのストリームがコネクション上で同時に走った場合、それぞれに割り当てられるリソースは

4 : 2 : 1 になることが期待されます。(あくまで割合の指定になります)


ストリームの依存

ストリームには依存関係が指定できます。

これは HEADERS Frame の Stream Dependency で指定が可能であり、省略可能でデフォルトは 0x0 です。

つまりデフォルトの状態では ID=0x0 の仮想的なストリームに依存することになります。

このため、ストリームの依存は、 ID=0x0 をルートとした木構造で表現することができます。

id=0

|
|- index.html(id=1, w=32)
|- style.css(id=3, w=16)
|- main.js(id=5, w=8)

しかし、 style.css/main.js は index.html あってこそなので、実際には index.html に依存していると考えることができます。

そこで、 style.css/main.js のストリームを index.html に依存させると以下のようになります。

id=0

|
|- index.html(id=1)
|- style.css(id=3, depends=1, w=16)
|- main.js(id=5, depends=1, w=8)

すると index.html のストリームが優先的に送られ、それが終わったら style.css/main.js のストリームが 2:1 の割合でリソースを分け合いながら送られることになります。

ただし、 index.html の転送中、コネクションレベルの Window Size が余っていても、ストリーム1 の Window Size が枯渇する可能性があります。その場合ストリーム 1 は WINDOW_UPDATE を受け取るまでブロックしますが、コネクションレベルで Windwo Size があるのであれば、その間に style.css/main.js を転送することができます。

親ストリームがブロックしても、依存するストリームはブロックしないということです。


排他フラグ(exclusive)

ストリームを依存に追加する際に、排他フラグ(exclusive)を使用すると、依存の階層を追加することができます。

例えば、以下の状態を考えます。

id=0

|
|- index.html(id=1)
|- main.js(id=3, depends=1, w=8)

ここで index.html に style.css を依存させるとき、排他フラグが無効ならこうなります。

id=0

|
|- index.html(id=1)
|- main.js(id=3, depends=1, w=8)
|- style.css(id=5, depends=1, w=8, exclusive=false)

しかし、有効なら、それを唯一の依存として、他を自分の子とするため、こうなります。

id=0

|
|- index.html(id=1)
|- style.css(id=5, depends=1, w=8, exclusive=true)
|- main.js(id=3, depends=1, w=8)

こうして、階層を追加することができるわけです。


優先度の更新

既に生成されているストリームの優先度は、 PRIORITY Frame で更新することができます。

単に weight を変更するだけであれば、依存内で比率を再計算すれば良いですが、依存先を変える場合は、木全体を更新する必要があります。

ここは、ドラフトの 5.3.3. Reprioritization の項をそのまま引用します。翻訳はこちらから。

例えば、BとCがAに、DとEがCに、FがDに依存する依存ツリーを考えます。AがDに依存関係を生成する場合、DはAの位置に移動します。Fを除く他の全ての依存関係はとどまり、再優先度付けが排他である場合は、全てがAに依存するようになります。

? ? ? ?
| / \ | |
A D A D D
/ \ / / \ / \ |
B C ==> F B C ==> F A OR A
/ \ | / \ /|\
D E E B C B C F
| | |
F E E
(中間状態) (非排他的状態) (排他的状態)

また Stream が終了した場合は木から外され、依存が繰り上がります。

他にも細かい状態管理について規定されていますが、詳細は 5.3.4. Prioritization State Management を参照ください。


まとめ

今のところ、 Chrome や FireFox でも実装はされておらず、 nghttp2 くらいでしか動いてなさそうです。 nghttp2 での挙動については How Dependency Based Prioritization WorksHTTP/2 のプライオリティ制御が与える影響の視覚化 で解説されています。

最初にも言いましたが、 PRIORITY 関連のパラメータは全てサーバへの提案であり、サーバがその通り処理するとは限りません。緩くサーバを実装する際は、まず丸っと無視するという戦略が考えられると思います。