HTTP/2, SPDY 対応の負荷テストツール h2load

HTTP/2, SPDY に対応する負荷テストツール h2load を紹介します.  HTTP/1 用の負荷テストツールは ab や weighttp が有名ですが, これらは HTTP/2 や SPDY には対応していません.  HTTP/2, SPDY を負荷テストする場合は h2load を使うことができます.

h2load は [nghttp2](https://github.com/tatsuhiro-t/nghttp2) リポジトリに入っています.  SPDY プロトコルをサポートするには, [spdylay](https://github.com/tatsuhiro-t/spdylay) をリンクする必要があります.  そのため最初に spdylay をインストールしてから nghttp2 をインストールします.
インストール方法はそれぞれの README を見てください.

この記事の執筆時点では HTTP/2 はまだ策定途中です.  h2load は nghttp2 がサポートする HTTP/2 ドラフトバージョンのみに対応し, 執筆時点のバージョンは [HTTP/2 draft-11](http://tools.ietf.org/html/draft-ietf-httpbis-http2-11) になります.

それでは h2load の基本的な使い方を見て行きましょう. h2load は weighttp の UI に似せて開発されています.  よってお馴染みの `-n` と `-c` オプションが同じように使うことができます.  `-n` オプションは発生させる HTTP リクエストの総数を指定します.  `-c` オプションは並行クライアント数を指定します.

h2load ではもうひとつ重要なオプションがあり, それは `-m` オプションです.  HTTP/2, SPDY では 1 接続上に複数の HTTP リクエストを多重化することができます.
`-m` オプションは HTTP リクエスト多重化の最大値を設定します.  例えば, `-m100` とすると,  h2load は最大 100 個の HTTP リクエストを多重化するようになります.  この値はサーバーの許可する最大値以下に設定する必要があります.

それでは実際に h2load を使った例を見てみましょう.  SPDY/3.1 を有効にした nginx がポート 443 で HTTP リクエストを待ち受けているとします.  リクエスト総数 100,000, 並行クライアント数 100, リクエスト多重度 100 で負荷をかけた場合の例は以下のようになります:

    $ h2load -n100000 -c100 -m100 https://localhost/
    starting benchmark...
    spawning thread #0: 100 concurrent clients, 100000 total requests
    progress: 10% done
    progress: 20% done
    progress: 30% done
    progress: 40% done
    progress: 50% done
    progress: 60% done
    progress: 70% done
    progress: 80% done
    progress: 90% done
    progress: 100% done

    finished in 3 sec, 630 millisec and 29 microsec, 27547 req/s, 24512 kbytes/s
    requests: 100000 total, 100000 started, 100000 done, 100000 succeeded, 0 failed, 0 errored
    status codes: 100000 2xx, 0 3xx, 0 4xx, 0 5xx
    traffic: 91117402 bytes total, 28300414 bytes headers, 61200000 bytes data

合計 100,000 のリクエストを 3.630 秒で処理したという結果になりました.  最終行の traffic の値ですが, total はアプリケーションデータとして受信したバイト数です.  この例の場合 SSL/TLS で暗号化を解いた後のアプリケーションデータになります.  headers は SPDY の場合 SYN_REPLY フレームのペイロードサイズの合計になり, data はレスポンスボディの合計になります.

https URI を使う場合に SSL/TLS 上で使うアプリケーションプロトコルは, TLS 拡張である [ALPN](http://tools.ietf.org/html/draft-ietf-tls-applayerprotoneg-05) または [NPN](https://technotes.googlecode.com/git/nextprotoneg.html)  でネゴシエーションすることになります.
ALPN と NPN の大きな違いは, ALPN ではクライアントが送信するアプリケーションプロトコルのリストの中から,  サーバーがアプリケーションプロトコルを選択するのに対し, NPN ではサーバーが送信するリストの中から,  クライアントがアプリケーションプロトコルを決定します.  ALPN/NPN ではアプリケーションプロトコルに識別子が付けられており (ALPN 識別子と呼びます),  例えば, SPDY/3.1 は `spdy/3.1`, HTTP/1.1 は `http/1.1` のようになります.  HTTP/2 は策定終了後は `h2` と呼ばれる予定です.  ドラフト段階の試験実装の場合,  ドラフト番号を付加して `h2-11` のように呼ばれています.  先の例では, h2load と nginx 間でネゴシエーションされ, `spdy/3.1`,  すなわち SPDY/3.1 で通信するということになっています.
NPN は OpenSSL 1.0.1g でサポートされています.  ALPN は OpenSSL 1.0.2 beta1 からサポートされています.  OpenSSL 1.0.2 beta1 を使う場合は, Heartbleed 脆弱性を回避するため `-DOPENSSL_NO_HEARTBEATS` をつけてビルドすることをお忘れなく.  ALPN, NPN を有効にするには, クライアントとサーバー両方がサポートしている必要があります.

http URI を使う場合 ALPN/NPN は使えず, h2load はデフォルトでは HTTP/2 で通信します.  アプリケーションプロトコルを変更するには `-p` オプションを使い使用するアプリケーションプロトコルの ALPN 識別子を指定します.  例えば SPDY/3.1 を使う場合, `-pspdy/3.1` と指定します.  このように http URI を使う場合はアプリケーションプロトコルのネゴシエーションは行われないので,  h2load とサーバー間で使用するアプリケーションプロトコルを事前に設定しておく必要があります.

サーバーを負荷テストをする場合にクライアントがボトルネックになっていては負荷テストになりません.  h2load では `-t` オプションで同時スレッド数を指定することができるので, マルチコア PC では h2load 自体がボトルネックになる可能性を低減できます.

HTTP/2 と SPDY/3 以降ではフローコントロールという機構がプロトコルに備わっています.  これは一度に自由に送信可能なデータ量を制限し, サーバーやプロキシーなどでのメモリ使用量を抑えることが目的のものです.  予め受信側がどれだけデータを送信できるか送信側に通知しておきます.  これをウインドウサイズと呼びます.  送信側はデータを送る毎にウインドウサイズを送った分だけ減らします.  送信側はウインドウサイズが 0 になるとこれ以上送信できません.  受信側はデータを受信し処理しますが, 適宜送信側に新たに利用可能なウインドウサイズの増分を知らせます.  送信側は通知された増分をウインドウサイズに加算することで次のデータが送信可能になります.  HTTP/2 と SPDY/3 以降では各ストリーム (1 接続上で多重化された HTTP リクエストとレスポンスのこと) でフローコントロールが適用されていて, HTTP/2 と SPDY/3.1 ではこれとは別に接続毎にフローコントロールが行われています.  フローコントロールは送信するデータ量に影響を与えるので負荷テストの実行時間にも影響がでる場合があります.
HTTP/2 のウインドウサイズの初期値は 65,535 バイト, SPDY は 65,536 バイトになっています.  h2load ではストリーム, 接続のウインドウサイズをそれぞれ `-w`, `-W` オプションを使って変更することができます.  `-w`, `-W` に指定するのはウインドウサイズのビット数です.  例えば, `-w20` と指定すると, SPDY ではストリームのウインドウサイズが ``1 << 20`` = 1,048,576 バイトになります.  HTTP/2 では既定値との整合性を取るため -1 するので, 20 を指定した場合 1,048,575 バイトになります.