ブログ・ア・ラ・クレーム

技術的なメモとかライフログとか。

HTTP/2 のコネクション再利用について確認してみる

はじめに

本記事は http2 Advent Calendar 2015 の 12 日目の記事となります。

本記事では HTTP/2 における TCP コネクション再利用とその周辺仕様を確認してみようと思います。コネクション再利用の挙動を理解することは、実際の Web サイトにおける HTTP/2 のデプロイの助けになると思われます。

HTTP/1.1, HTTP/2 での TCP コネクション管理

よく訓練された方々には既知のことかとは思いますが、 HTTP/2 において HTTP/1.1 の頃にあった性能向上の為のハックであるドメインシャーディングは好ましくないテクニックになってしまいます。 HTTP/1.1 において今日のブラウザは 1 ドメインあたり概ね 6 TCP コネクションほど張って、並列にリクエストを送るような振る舞いをします。このため Web サイトなどで参照するドメインを分割することで HTTP リクエストの並列度を向上する手法がよく取られていました。 しかしながら HTTP/2 において HTTP リクエストの処理は 1 本の TCP コネクションの上で、ストリームという論理単位で HTTP リクエストが並列処理されるようになり、このようなハックは不要になりました。むしろ参照するドメイン名が集約されていた方が、 TCP コネクションを張る回数が削減できて性能の向上に貢献できます。

これは悩ましい問題です。今日までに HTTP/1.1 を前提としたチューニングがされた Web ページの構造を HTTP/2 向けに構築し直すのは骨が折れる作業といえます。また、依然として HTTP/1.1 ユーザも数多くいるわけで、現在 HTTP/2 前提の Web ページ構成にしてしまって良いのかどうかの判断も難しいといえます。 本記事ではこの課題に対する一つの対策となりうる、 HTTP/2 のコネクション再利用呼ばれる振る舞いについて記述します。

HTTP/2 におけるコネクション再利用

RFC7540 Section 9.1.1 Connection Reuse の節には下のような記述があります。

   Connections that are made to an origin server, either directly or
   through a tunnel created using the CONNECT method (Section 8.3), MAY
   be reused for requests with multiple different URI authority
   components.  A connection can be reused as long as the origin server
   is authoritative (Section 10.1).  For TCP connections without TLS,
   this depends on the host having resolved to the same IP address.

   For "https" resources, connection reuse additionally depends on
   having a certificate that is valid for the host in the URI.  The
   certificate presented by the server MUST satisfy any checks that the
   client would perform when forming a new TLS connection for the host
   in the URI.

https://tools.ietf.org/html/rfc7540#section-9.1.1

HTTP/2 において、 オリジンが authoritative であれば既に張られているコネクションを再利用できるのです。 再利用できるかどうかの基準については、再利用しようとしているコネクションにおいて、リクエスト対象のドメイン名がサーバ証明書の subjectAltName にマッチするかどうかで判断します。 例えば、まず https://a.example.com/ にリクエストを投げるために a.example.com にコネクションを張った際にサーバ証明書の subjectAltName に "*.example.com" が含まれている場合、続いて https://b.example.com/ にリクエストを投げる際に以前使ったコネクションを再利用することができます。

f:id:syu_cream:20151211224939p:plain

この仕様により、冒頭で述べた HTTP/1.1 の既存のハックの改修を行わずに HTTP/2 のコネクション集約の恩恵を受けられる可能性が出てきます。 これを利用して、例えば利用ドメインと証明書を集約して持つエッジサーバなどを構築し、 TCP コネクションを束ねて受けてしまうなどの対応が行えます。

Chrome における コネクション再利用

Google Chrome は既にコネクション再利用のロジックが入っているようです。いくつかの Google の Web ページを閲覧した後 HTTP/2 セッションのステータスを chrome://net-internals/#http2 で確認したところ、下記のようになっていました。

f:id:syu_cream:20151211214255p:plain

いくつかのドメイン名の HTTP/2 セッションが集約されていることがわかります。 ただし、 サーバ証明書の subjectAltName としては *.google.com が含まれているのですが、これにマッチして集約されることが期待されるいくつかのセッションが分割されています。

コネクション再利用の落とし穴

このコネクション再利用ですが、サーバとしては望まないコネクションの再利用が行われる可能性があります。 RFC7540 で記述されている通り SNI を元にリクエスト転送先オリジンサーバを決定している HTTP/2 プロキシサーバが存在するかもしれませんし、サーバ証明書としてはワイルドカードの subjectAltName を持っているものの、実際にはレスポンスを返せない場合があります。 このような場合には、サーバはクライアントに対し 421(Misdirected Request) を送って通知することが可能です。

f:id:syu_cream:20151211224943p:plain

ただし、クライアントがこの挙動を考慮して「コネクションを再利用してリクエストを投げてみて 421 が返されてから、再度コネクションを張り直してリクエストを投げ直す」ような挙動をした場合、もちろんレイテンシが向上してしまいます。 この問題を解決するために、 Origin フレーム という HTTP/2 の拡張フレームの Internet-Draft も存在します。 Origin フレームを用いることでサーバ証明書としては集約されてしまうオリジンがあったとしても、サーバが実際に authoritative なオリジンを指定することができ、これにより上記のレイテンシ向上問題を回避することができます。 (Origin フレームは拡張仕様であるためサーバ、クライアント両者が対応していなければならず、また大量のオリジンに対して authoritative であるサーバは Origin フレームにそれらを列挙し送信すると、その送信自体のコストが大きくなってしまいそうな欠点はありますが。。。)

f:id:syu_cream:20151211224947p:plain

おわりに

HTTP/2 のコネクション再利用が期待できると、現在の Web にデプロイした際に HTTP/2 の恩恵が受け易くなると思われます。 ただし現状クライアントの実装と周辺仕様の充実、さらに実際に運用した上で得られた知見が必要である状態だと思われます。

Apache Traffic Server の設定やちょっとした制御ロジックを mruby で書けるようになるかもしれないプラグイン書いてる

mod_mrubyngx_mrubyh2o_mrubylibvmod_mruby など、さまざまな HTTP サーバの設定などを mruby スクリプトで書ける世の中になりつつありますね。 そんな風潮の後押しもあり、近頃、題意の通り mruby で ATS(Apache Traffic Server) の設定やヘッダの加工などのロジックを mruby で記述可能にするプラグインts_mruby をちまちまと開発しています。

ts_mruby によって例えば下記のようなロジックが mruby で書けるようになります。

  • リクエストヘッダを付与したり削除する
if server_name == "NGINX"
  Server = Nginx
elsif server_name == "Apache"
  Server = Apache
elsif server_name == "ApacheTrafficServer"
  Server = ATS
end

# rewrite request headers
r = Server::Request.new
conn = Server::Connection.new
r.headers_in["X-ATS-Plugin"] = "ts_mruby"
r.headers_in["X-Forwarded-For"] = conn.remote_ip
r.headers_in.delete("Cookie")
  • 特定 IP 以外からのリクエストを弾く
if server_name == "NGINX"
  Server = Nginx
elsif server_name == "Apache"
  Server = Apache
elsif server_name == "ApacheTrafficServer"
  Server = ATS
end

whitelist = [
  "127.0.0.1"
]

# deny if client IP isn't listed in whitelist
conn = Server::Connection.new
unless whitelist.include?(conn.remote_ip)
  Server::echo "Your access is not allowed ..."
  Server::return Server::HTTP_FORBIDDEN
end

ATS でプラグインをロードする際、グローバルプラグイン(ATS 全体に作用するプラグイン)や Remap プラグイン(各リクエストをプロキシする際に作用するプラグイン)という形態を採ることになるのですが、 ts_mruby ではそのどちらもサポートしています。 全リクエストで動作させたいのか、特定 VirtualHost や パスだけに対して動作させたいのか、など要件によって使い分けることが可能です。

  • グローバルプラグインとして使う場合、 plugin.config に下のように記述
# write here if you hope to apply all of requests
ts_mruby.so etc/trafficserver/unified_hello.rb
  • Remap プラグインとして使う場合、 remap.config に下のように記述
# write here if you hope to apply only accesses to /test
map /test http://127.0.0.1/ @plugin=ts_mruby.so @pparam=etc/trafficserver/unified_hello.rb

現状サポートしている ts_mruby の mruby 向けクラス、メソッド は mod_mruby, ngx_mruby と共通の名前、機能にするよう気をつけています。 mruby を HTTP サーバ設定の DSL として使うこともできるようになるかもしれません(ただし各サーバ固有の機能の部分はどうしても出てくるわけで、その部分をどう扱うか悩みどころです)

ts_mruby は現状、細々と個人で開発をしている状態です。なにかご意見や Pull-Request などいただけると幸いです。

JMeter で HTTP/2 リクエストを送れるようにするプラグインを書いてみた

表題の通り、 JMeter で HTTP/2 リクエストを送ることを可能にする HTTP2 サンプラーを追加するプラグインを書いてみました。

github.com

但し、今のところ単純な GET リクエストを、しかも 1 request / connection で送る程度のことしかできず、パフォーマンステストをする上で必要な機能が十分にあるとは言えない状態です。 HTTP ヘッダマネージャによるリクエストヘッダの編集、アサーションでのレスポンスコード、レスポンスメッセージ、レスポンスヘッダのチェックはできるので、何か単純なサーバの動作確認を実施する程度になら使えるかも知れません。

プラグインを導入すると、下図のように HTTP/2 Sampler という項目が追加されます。 HTTP/2 Sampler を追加して項目を埋めていくことで HTTP/2 リクエストの送信が実現できます。 f:id:syu_cream:20150706002423p:plain

HTTP サンプラーで使われるヘッダマネージャを、 HTTP/2 Sampler でも使用できます。 f:id:syu_cream:20150706002039p:plain

HTTP/2 Sampler では現在、サーバの IP 、ポート、パスを指定できるようにしています。 今後必要な項目を用意していきたいところです。(レイアウトが残念なことになっているのでそれもどうにかしたい f:id:syu_cream:20150706002832p:plain

テスト結果はアサーションで検査したりリスナーで一覧、保存できたりします。 jtl ファイルに出力させれば、 Jenkins の Performance Plugin に解釈させることも可能なはず。 f:id:syu_cream:20150706002817p:plain

RFC 7541 (HPACK) の日本語訳を公開しました

表題の通り、 RFC7541 "HPACK: Header Compression for HTTP/2"日本語訳されたドキュメント を公開しました。 今のところ GitHub の僕のリポジトリにて編集管理を行っています。(将来的には別の管理の仕方をするかもしれません)

前回日本語訳を行った時点(draft-10) から RFC になるまでの、記述上の細かい修正の追従が主になります。また、前回イマイチだった箇所の日本語訳も少し修正しています。

問題点の指摘や翻訳の改善の Pull-Request 、大歓迎です。 こちら よりお願いします。

末筆ですが、やっと HTTP/2 , HPACK の RFC が出ましたね!これまで変更を追従してきた皆様お疲れ様でした。

HPACK draft-10 の日本語訳を公開しました

表題の通り、 HPACK draft10日本語訳されたドキュメント を公開しました。 今のところ GitHub の僕のリポジトリにて編集管理を行っています。(将来的には別の管理の仕方をするかもしれません)

問題点の指摘や翻訳の改善の Pull-Request 、大歓迎です。こちら よりお願いします。

HTTP Alternative Services について

本稿は HTTP2 Advent Calendar 2014 20 日目の記事です。 本稿では HTTP/2 周辺のトピックでもやや地味な部類に入るであろう、 HTTP Alternative Services について簡単に触れていきます。

  • 2014-12-21 19:20 用語の修正

概要

HTTP Alternative Services とは、 HTTP で配信するリソースを他のプロトコル、ホスト、ポート番号でもサービス提供ができることを通知する仕組みです。 用途としては下記のようなものが想定されます。

  • メンテナンス等のためサーバをダウンさせる前に、他のサービス可能なサーバを提示する
  • 新しいプロトコルへの切り替えを提案する(HTTP/1.1 から HTTP/2 へ、など)
  • 日和見暗号(サーバ認証を行わず http スキームで暗号化通信する仕組み) の使用を提案する
  • SNI などをサポートするクライアントとそうでないクライアントを分離する
  • 他ホストにリクエストを振り分けて負荷分散する

HTTP Alternative Services は HTTP/2 とは分離された Internet-Draft になっており、 2014年12月現在、 5番目のドラフトが出ている 状態です。

HTTP Alternative Services メッセージの表現方法

HTTP Alternative Services ではサーバからクライアントへ代替が存在する旨のメッセージを通知することで使用することができます。 このメッセージでは最初にリクエストを受け取った OriginRFC6454 にあるようにスキーム、ホスト、ポートの組で識別する)を代替する Alternative service を通知するような内容になります。

例えば Origin が下記の通りだったとして、

 ("http", "www.example.com", "80")

Origin が下記のようなメッセージを送ることで、「new.example.com:81 に HTTP/2 で話すことで Origin と同様のリソースを受け取ることができる」ことを通知できます。

("h2", "new.example.com", "81")

このメッセージの表現方法について、現行のHTTP Alternative Services のドラフトでは下記2つの方法が提示されています。

  • Alt-Svc HTTP ヘッダフィールド を使用する
  • HTTP/2 において ALTSVC フレーム という拡張フレームを使用する

Alt-Svc HTTP ヘッダフィールド

Origin は、下記のようなフォーマットの Alt-Svc ヘッダを送ることで Alternative service の存在をクライアントに通知することができます。 ここでプロトコル名として提示するのは、 ALPNプロトコル識別子と同様のものになります。

Alt-Svc: h2="new.example.org:80"

Alt-Svc ヘッダの内容はキャッシュされます。デフォルトでは 24 時間後にフレッシュされますが、 Alt-Svc ヘッダの ma(max-age) パラメータによってキャッシュが失効されるまでの時間を指定することもできます。

# 1時間後に失効
Alt-Svc: h2=":443"; ma=3600

なお、クライアントは Alt-Svc を使って Alternative service にリクエストを投げる際は Alt-Used HTTP ヘッダフィールド を付加する必要があります。(MUST) これは Alternative service 切り替えのループが発生することを防ぐことと、サーバ側で負荷分散する際に Alt-Svc が使われたリクエストなのかどうかを識別することが目的です。

Alt-Svc を使って振られた Alternative service はリクエストをうまく捌けない際に 421 Misdirected Request HTTP ステータスコード を返します。 クライアントは 421 を受け取った際には Alt-Svc キャッシュから該当エントリを消す必要があります。(MUST)

ALTSVC フレーム

HTTP/2 で Alternative Services を使用する場合は、 ALTSVC フレームで Alt-Svc ヘッダと同内容のメッセージを表現します。 ALTSVC フレームは Optional なフレームタイプであり、 最新の HTTP/2 のドラフト では定義されておらず、これをサポートしていないクライアントはサーバから送られてきた ALTSVCフレームを無視しても問題ありません。 (ちなみに draft-11 から draft-13 の間は ALTSVC フレームタイプが HTTP/2 のドラフト内で定義されていたりしました)

HTTP Alternative Services の活用例

http スキームで暗号通信を行う 日和見暗号 でも HTTP Alternative Services を使用します。 Origin は日和見暗号が利用可能であればレスポンスに HTTP-TLS ヘッダを付与します。 クライアントはこれを ":443" の Alternative service として記憶することで、次回以降にユーザが http スキームでリクエストを投げる際に HTTP over TLS で通信可能な Alternative service を選択することができます。

f:id:syu_cream:20141220153046p:plain

HTTP/2 通信開始前は基本的に ALPN や HTTP Upgrade でクライアントとサーバの間でお互いに HTTP/2 を解釈可能だということを確認します。 HTTP Alternative Services を使って事前に HTTP/2 で通信可能な alternative services がいることを知っていれば、このようなプロトコルネゴシエーションをスキップして直ぐに HTTP/2 通信を開始することもできます。

f:id:syu_cream:20141220153103p:plain

  • 負荷分散する

alternative service として別ホストを指定することもできるため、 Origin が高負荷な場合は HTTP Alternative Services を使って別ホストにリクエストを振ることで負荷分散することも可能です。

f:id:syu_cream:20141220153112p:plain

これは個人の意見ですが、 HTTP Alternative Services を使って負荷分散するのは難しいのではと思っています。 まず Origin は Alternative services の負荷状況を把握しておく必要があると考えられます。でないと誤って高負荷な Alternative services にリクエストを振り続けてしまう可能性があります。 更に HTTP Alternative Services は Optional な機能のため、これで負荷をどの程度分散できるかはどの程度ブラウザが対応してくれるかに大きく依存します。

HTTP Alternative Services 周辺事情のこれまで

HTTP Alternative Services 周辺のこれまでのおおまかな流れをまとめてみました。

  • Alternate-Protocol ヘッダフィールド

Alternative Services の前身となるのは、 SPDYの仕様に含まれる Alternate-Protocol ヘッダフィールド であると思われます。 Alternate-Protocol ヘッダは、リクエストが指定ポートで他のプロトコルでも捌けることを通知するためのヘッダです。

  • Encryption for HTTP URIs Using Alternate Services

こちらの文書日和見暗号を実現するのに、 Alt-Svc ヘッダを使う提案がされています。

  • HTTP Alternate Services

こちらの文書 で、 Alternative Services が日和見暗号から分離されています。

  • HTTP/2 ALTSVC フレーム

http2 draft-11 で、 HTTP/2 で Alternative Services を使用するためのフレームタイプが定義されました。 しかしながら http2 draft-13 でこれは除去され、 HTTP Alternative Services draft-02 に分離されました。

GitHub の issues で議論された内容を元に、 HTTP Alternative Services draft-05 まで更新されています。

おわりに

HTTP Alternative Services の理解があまりなかったため、今回仕様と経緯を調査しまとめてみました。補足や誤り指摘等大歓迎です。何かありましたらコメント欄にでも書いて頂ければ幸いです。

ちなみに今回 HTTP Alternative Services に対応している実装が存在するのか気になって少々調べてみたのですが、少なくともコードベースでは Chromium, FireFox, nghttp2 には対応する実装がありそうでした。 どこまで機能するのかは未調査です。後ほど余裕があった際にでも調べます。

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

Maygh: Building a CDN from client web browsers (EuroSys'13) を読んだ

はじめに

本稿は システム系論文紹介 Advent Calendar 2014 14 日目の記事です。

Maygh: Building a CDN from client web browsers (EuroSys'13) という論文を読みました。ざっくりと内容紹介や所感を記述します。

概要

Maygh は Web ページで要求される静的リソースを、既にそのリソースを持っている他のクライアントから P2P 通信で受け取ることによりサーバ側の帯域使用量を低減するシステムです。 Maygh は JavaScript で記述されたクライアントスクリプトという形でユーザに提供され、 WebRTC などを使ってクライアント間通信を実現します。その他専用のブラウザのプラグインなどを導入する必要はありません。 ただしコンテンツ配信者は coordinator と呼ばれるキャッシュ保持情報と保持するクライアントの IP アドレスを教えるためのサーバを提供する必要があります。 各クライアントはリソースを取得する際にまず coordinator に問い合わせ、それのキャッシュを持つ他クライアントがいれば coordinator からもらった情報を元に WebRTC 接続を確立して要求されるリソースのやり取りを行うことになります。

Maygh の評価として、ショッピングサイト Estyアクセスログを用いたシミュレーション結果が提示されています。 このシミュレーション結果によると、 Esty のワークロードにおいて約 75 %ほど帯域使用量を削減することができるとのことです。

従来のコンテンツ配信

大規模な Web サービスを運営する上で、コンテンツ配信に伴うネットワーク負荷を捌く方法は悩ましい問題です。 今日ではネットワーク設備を強化する、配信サーバの台数を増やす、静的コンテンツの配信に Akamai や Limelight などの CDN(Contents Delivery Network) を利用するなどの方法が存在します。 しかしながらこれらの方策は大きなコストが発生しがちです。

近年の別のアプローチとして、サーバ側設備を増強するのではなく、クライアント側で静的コンテンツの配信を共有し合ってもらい、サーバ側帯域使用量を提言する手法が現れてきています。 具体的な実装例として Akamai NetSession Interface などが存在します。 しかしながらこれらの既存手法は専用アプリケーションやブラウザのプラグインの導入を強いるものになっており、エンドユーザにとって導入障壁が高いものとなっています。

Maygh のデザイン

Maygh は幾つかのモダンなブラウザの提供する機能の支援を受けて、エンドユーザに専用プラグインなどの導入を強いることなくユーザ参加型のコンテンツ配信を行うシステムです。 Maygh を利用するにあたって、ユーザのブラウザには都合下記のような要件が発生します。

  • JavaScript が有効になっている

  • Indexed Database API, WebStorage をサポートしている必要がある

    • Maygh はこれら Storage API を用いて各クライアントの LocalStorage にキャッシュを保持する
  • WebRTC をサポートしている

    • Maygh クライアント間の P2P 通信に用いられる
    • WebRTC が使えない場合、 RTMFP を使用することも可能

Maygh は、各クライアントに配布される Maygh クライアントスクリプト と、コンテンツ配信者により提供される coordinator サーバ から構成されます。 Maygh クライアントは小サイズの JavaScript で実装されたスクリプトです。 RTMFP を用いるための小さな Flash オブジェクトも伴います。 coordinator は Maygh クライアントと各クライアントが持つコンテンツの対応関係を管理する client map と、 各コンテンツを持つオンライン状態のクライアントの情報を管理する content location map の二つのデータを持つサーバです。 Maygh の coordinator は性能がスケールできるよう複数台で動作できるよう設計されています。複数の coordinator を動作させる際は、それぞれの coordinator が content location map を持ち、自分にぶら下がっているクライアントの情報を管理するようになります。

Maygh による通信

Maygh によるクライアント間、そしてクライアントと coordinator 間の通信は下図の通りになっています。

f:id:syu_cream:20141214234254p:plain

図には含まれていませんが、クライアントは Web ページ初回アクセス時に coordinator にコネクションを張り、 Maygh の update メッセージで自身の持つコンテンツ情報を送ります。 各コンテンツの取得時に最初に lookup メッセージを送信し、それに対するレスポンス lookup-response をもって要求する他のクライアント(以降、ピア)の ID を取得します。 その後 coordinator に connect メッセージを送信し、コンテンツを所有するピアとの RTMFP/WebRTC セッションを確立します。 この際、多くの場合ピアの間には NAT デバイスが挟まっていることが想定されるので coordinator に STUN をしゃべってもらい、相手方ピアの IP アドレスとポート番号を教えてもらいます。 その後は RTMFP/WebRTC セッションを確立し、コンテンツの取得を行い、最後にコンテンツを取得完了した旨を update メッセージで coordinator に伝えます。

評価

実装

この論文では Maygh の評価を行う上で下記の実装を行ったとのことです。

また、 Maygh の実装は GitHub に公開されている ようです。

coordinator がスケールするかの検証

複数の coordinator プロセスを1台の検証用マシンで動作させた際と、複数のマシンで動作させた際の transaction/sec が検証されています。 検証の結果、複数マシンで動作させることにより coordinator 数に比例して捌けるトランザクション数が上昇しており、十分スケールするとのことです。

Maygh による帯域使用量削減効果の検証

Esty の 7 日間のアクセスログを用いた、 Maygh の効果のシミュレーション結果が提示されています。 この結果によると、 Maygh 導入により約 75% の帯域使用量削減効果が見られたようです。 また既存の専用プラグインを導入してクライアントサイドでコンテンツ交換を行う手法との比較として、「10% のユーザがそのプラグインを導入する」という想定での帯域使用量の削減効果も検証されていますが、こちらは約 7.8% と Maygh と比較して低い効果しか見られないとのことです。

余談: 最近の関連トレンド

この論文を読んでいて思い出したのですが、昨年に米 Yahoo! が PeerCDN という配信システムを持つ会社を買収した話 があったかと思います。 記事によると WebRTC で P2P 通信することでコンテンツ配信をするらしい話が記載されていることもあり、本論文に近い手法であるものかと推測されます。 PeerCDN のその後の話も気になりますね。 また最近ですと、あまり詳細な情報は出ていないようですが、 BitTorrent 社が BitTorrent を用いてコンテンツ共有を行う Web ブラウザ Maelstrom のアルファテストを開始したとのニュース があったかと思います。

P2P でクライアント間でコンテンツ配信負荷を負担し合ってサーバ側帯域使用量を減らす手法の今後の動向が気になるところです。