軽量な静的コンテンツ配信におけるHTTP/2とSPDYのWebサーバの性能を見てみよう

既存のHTTPやWebサーバの技術を見ているものとして、新しい技術も調査しておかないといけないなということで、今日はHTTP/2とSPDYでおしゃべり可能なWebサーバの性能を見てみたいと思います。

HTTP/2の実装としては、tatsuhiro-tさんのC言語実装ライブラリであるnghttp2に注目しており、今日はそのライブラリを使って実装されているWebサーバnghttpdを動かし、SPDY/3.1で動作しているnginxとの性能比較をしました。HTTP/2やSPDY/3.1はもちろんクライアント側も既存のベンチマークツールではおしゃべりできないので、nghttp2で実装されているh2loadを使用しました。weighttpと使い方が似ているため個人的には非常に使いやすいです。

tatsuhiro-tさんによって先行してベンチマークが公開されていますが、僕自身も自分の環境で色々と実験したり実装を弄っているので、実際に自分の環境を構築して試しました。

nghttp2の導入

今回は最初なのでnghttp2の導入を簡単に紹介します。基本的にはnghttp2のGitHubにドキュメントがあるので、それをみてインストールすれば問題ないでしょう。今回は評価対象にSPDYが含まれているのでh2loadをSPDY対応にするために、ビルドする際にSPDYライブラリのC実装であるspdylayが必要になります。それも合わせてビルドしましょう。

[program lang=’bash’ escaped=’true’]

export PKG_CONFIG_PATH=/lib/pkgconfig:/usr/lib/pkgconfig:/usr/lib64/pkgconfig:/usr/local/lib/pkgconfig
git clone https://github.com/tatsuhiro-t/spdylay.git
cd spdylay
autoreconf -i
automake
autoconf
./configure
make
sudo make install
cd ..
git clone https://github.com/tatsuhiro-t/nghttp2.git
cd nghttp2
autoreconf -i
automake
autoconf
./configure --with-spdylay
make
sudo make install

[/program]

これで今回必要なバイナリは準備できました。では実際にHTTP/2なサーバnghttpdを起動させてみましょう。今回はHTTP/2もTLS有りで起動させてSPDYとできるだけ近い条件で評価を行いたいと思います。

[program lang=’bash’ escaped=’true’]

nghttpd -n 2 -d `pwd` 8080 ssl/server.key ssl/server.crt

[/program]

とりあえず、dオプションによりカレントディレクトリをドキュメントルートにしました。そして、今回の検証環境はコア2つのメモリ8GBのVM上で動くFedora19ですので、nオプションによりworkerスレッドを2つ起動させるようにします。後はListenするポートを8080、private keyとcertficateファイルを指定します。

SPDY/3.1で動くnginxの導入

次にSPDY/3.1が動くnginxをビルドしましょう。nginxは皆さんご存知だと思うので導入は大部分省略しますが、nginx-1.5.11をダウンロードし以下のビルドオプションでビルドしました。

[program lang=’bash’ escaped=’true’]

./configure ¥
  --prefix=/usr/local/nginx-1.5.11 ¥
  --with-http_ssl_module ¥
  --with-http_spdy_module ¥
  --with-http_gunzip_module ¥
  --with-http_gzip_static_module ¥

[/program]

インストール後は以下のような設定ファイルを用意しました。もっと性能が出る設定があるかもしれませんが、今回はこの設定を前提に比較しようと思います。

[program lang=’ruby’ escaped=’true’]

worker_processes  2;

events {
    worker_connections  1024;
    accept_mutex_delay 100ms;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile on;
    tcp_nopush on;
    access_log off;

    # SPDYテスト時はgzip関連はコメントアウト
    gzip on;
    gzip_comp_level 9;

    keepalive_requests 10000;
    keepalive_timeout 65;

    server {
        listen       81;
        server_name  localhost;

        location / {
            root   html;
            index  index.html index.htm;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

    server {
       listen       8081 ssl spdy;
       server_name  localhost;

       spdy_headers_comp 9;

       ssl_certificate      ssl/server.crt;
       ssl_certificate_key  ssl/server.key;

       ssl_session_cache    shared:SSL:1m;
       ssl_session_timeout  5m;

       ssl_ciphers  HIGH:!aNULL:!MD5;
       ssl_prefer_server_ciphers  on;

       location / {
           root   html;
           index  index.html index.htm;
       }
    }
}

[/program]

h2loadとweighttpによるベンチマーク

では早速ベンチマークにとりかかりましょう。まずは一旦、nginxをSPDYでない状態(上記のポート81でのListen設定)で起動させてweighttpでベンチマークを書けてみます。テスト環境はIntel i7-4770K CPU @ 3.50GHzのコア2つとメモリ8GBのFedora19がVMwareのVM上で動いています。また、今回はヘッダ圧縮や並列ストリームの効果を見たいので、ヘッダの量の影響を大きくするためにContent-Lengthが21byte程度の軽量な静的コンテンツをリクエスト対象にしました。

HTTP/1.1のnginx

まずは、SPDYでないnginxに対して同時接続数100、総接続数10万のkeepalive有りでベンチマークをかけます。

[program lang=’bash’ escaped=’true’]

$ weighttp -k -c 100 -n 100000 http://127.0.0.1:81/index.html
weighttp - a lightweight and simple webserver benchmarking tool

starting benchmark...
spawning thread #1: 100 concurrent requests, 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 2 sec, 255 millisec and 990 microsec, 44326 req/s, 11124 kbyte/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: 25700000 bytes total, 23600000 bytes http, 2100000 bytes data

[/program]

44326 request/secとなりました。このテスト環境上は非常に速いです。

SPDY/3.1のnginx

続いて、SPDY/3.1なnginxに対してh2loadでベンチマークをかけます。同時接続数100、総接続数10万で、SPDYやHTTP/2は各セッション上で処理する並行ストリームの数をクライアントから指定できるので、今回は同時接続数に合わせて並行ストリーム数を100にしました。このストリーム数を変化させる事で大きく性能が上下する事は言うまでもありません。

[program lang=’bash’ escaped=’true’]

$ h2load -n100000 -c100 -m100 https://127.0.0.1:8081/index.html
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 1 sec, 293 millisec and 127 microsec, 77331 req/s, 2727 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: 5215400 bytes total, 1511000 bytes headers, 2100000 bytes data

[/program]

77331 request/secとなりました。keepalive有りのHTTP/1.1の静的配信と比べて約2倍の性能がでています。また注目すべきはベンチマークの最終行の転送量ですが、HTTP/1.1のリクエスト時のヘッダー転送量が23600000bytesなのに対し、SPDY/3.1ではヘッダ圧縮の効果により1511000bytesとヘッダの転送量が15分の1程度になっています。

HTTP/2かつTLSのnghttpd

では最後に、HTTP/2かつTLSなnghttpdに対してh2loadでベンチマークを書けてみます。ベンチマークのパラメータはSPDY時と同様です。

HTTP/2のリクエストなので、一旦ここでレスポンスまでの流れを確認しましょう。ザーッと書きます。

まずは、現状はNPNでh2-10が返ってくるはずです。また、NPN negotiated後にクライアントからSETTINGSフレームで並行ストリームの数やウィンドウサイズの数を送り、続いてリクエストのHEADERSフレームを送ります。その後、サーバ側からクライアントで指定した設定に関するSETTINGSフレームが返って来て、クライアントからACKとして再度SETTINGSフレームを送り、サーバからACKでSETTINGSフレームが返って来ます。そして、サーバからレスポンスのHEADERSフレームが返ってきた後、続いてコンテンツのデータがDATAフレームとして返って来ます。サーバから送るべきデータの送信が完了したら、続いてサーバからEND_STREAMがDATAフレームとして送られてきます。それを受けたクライアントはGOAWAYフレームを送信して接続を完了します。

では、ベンチマークの結果を見てみましょう。

[program lang=’bash’ escaped=’true’]

$ h2load -n100000 -c100 -m100 https://127.0.0.1:8080/index.html
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 0 sec, 797 millisec and 384 microsec, 125410 req/s, 3094 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: 4928985 bytes total, 426885 bytes headers, 2100000 bytes data

[/program]

125410 request/secとnginxのSPDYやHTTP/1.1と比較して、非常に速い事がわかります。というよりも、この検証環境の静的ファイル配信でこんな値を見たことが無いレベルです。ヘッダーのサイズはSPDYの1511000bytesよりも更に少なくSPDYのヘッダの3分の1の426885bytesとなっており、HTTP/1.1のヘッダの転送量から見ると45分の1になりました。この1セッション時に使用する並列ストリームを100から減らしていくと性能がどんどん劣化し、並列ストリーム数を1つにすると大体nginxのHTTP/1.1程度の性能になることも確認しました。このセッション単位の並列ストリームのアーキテクチャとヘッダ圧縮が、軽量な静的コンテンツ配信の性能に大きく貢献している事が分かります。

最後に上記のベンチマーク結果を表にまとめておきます。

nginx HTTP/1.1 nginx SPDY/3.1 nghttpd HTTP/2 + TLS
Request/sec 44326 77331 125410
Headers Traffic(kbytes) 23600 1511 426.885

まとめ

このように、今回はHTTPプロトコルの新しい技術であるHTTP/2やSPDY/3.1の軽量な静的ファイル配信の性能を確認してみました。並列ストリームやヘッダ圧縮、データ転送のバイナリ化によって、このようなベンチマーク環境及び条件においては性能が大きく向上している事がわかりました。また、今回は単一のクライアントからのベンチマークといった、条件の違いでプロトコルの有利不利が生じると思うので、様々なコンテンツ配信の特徴に合わせた設計は今後も必要だと思います。

それにしても、HTTPの今後が楽しみです。