読者です 読者をやめる 読者になる 読者になる

人間とウェブの未来

「ウェブの歴史は人類の歴史の繰り返し」という観点から色々勉強しています。

mrubyでHTTP/2の画像変換サーバを作った

Webサーバ プログラミング 研究

この記事は、mruby advent calendar 2015の16日目の記事です。

画像やstaticコンテンツ配信系はHTTP/2が有利な状況が幾つかあるので、ついでにHTTP/2を喋る画像変換サーバのプロトタイプをmrubyで作ってみました。ベースはもちろんtrusterdです。なんていったってmrubyのHTTP/2サーバですからね!!

最近また開発を再開しておりまして、昔はh2oやnghttp2のベンチマークに一緒に比較対象として入れてもらったりしていたのですが、しばらく離れているうちに皆さん先へ先へと行ってしまわれたので、また追いつけるようにセッセと勉強しながら実装しだしております。

github.com

その他、trusterdについてはこの辺とか、

qiita.com

この辺を見ていただくと良いかと思います。

hb.matsumoto-r.jp

trusterdのビルド

まずは早速以下のようにビルドしていきましょう。

  • build_config.rb

cloneしたtrusterd/にあるbuild_config.rbに、今回は画像変換するためのmgemをリンクするべく下記のmgemを追記します。

  conf.gem :github => 'kjunichi/mruby-mrmagick' 
  • build

後はビルドするだけです。環境はubuntu14_04のコア4つメモリ8GBのVMWareを使いました。

make
make install
  • build/conf/trusterd.conf

installすると、デフォルトではカレントディレクトリのbuild/以下に必要なファイルがインストールされるので、その中にあるtrusterdの設定ファイルを以下のようにしました。

この設定によって、あっという間にHTTP/2 + TLS + 画像変換の動きをします。簡単ですね。

アクセス対象のPNGファイルに対して、image.png?type=halfなどとクエリを与えてアクセスすると、画像を半分にしてレスポンスするような動きをします。今回はプロトタイプということで、簡単にそれのみの動きをするだけにとどめました。

SERVER_NAME = "Trusterd"
SERVER_VERSION = "0.0.1"
SERVER_DESCRIPTION = "#{SERVER_NAME}/#{SERVER_VERSION}"

root_dir = "/home/matsumotory/DEV/trusterd/build"

s = HTTP2::Server.new({                                                          
                                                                                 
  :port           => 8080,                                                       
  :document_root  => "#{root_dir}/htdocs",                                       
  :server_name    => SERVER_DESCRIPTION,                                         
  :run_user       => "matsumotory",                                              
                                                                                 
  :worker         => "auto",                                                     
                                                                                 
  :tls            => true,                                                       
  :key            => "#{root_dir}/ssl/server.key",                               
  :crt            => "#{root_dir}/ssl/server.crt",                               
                                                                                 
  :callback => true,                                                             
})  

s.set_map_to_storage_cb do
  r = s.r
  if File.extname(r.filename) == ".png"
    unless r.args.empty?
      # クエリパラメータは一つで?type=halfであること前提でとりあえず実装
      type = r.args[1..-1].split("=")[1]
      if type == "half"
        new_file = r.filename + "_" + type + ".png"
        unless File.exist? new_file
          img = Mrmagick::ImageList.new(r.filename)
          new_img = img.scale(0.5)
          new_img.write(new_file)
        end
        r.filename = r.filename + "_" + type + ".png"
      end
    end
  end
end

s.run

ブラウザからHTTP/2アクセスできるようにTLS関連ファイルも以下のように試しに作っておきます。

cd build/ssl
sudo sh -c 'yes "" | openssl req -new -days 365 -x509 -nodes -keyout server.key -out server.crt'

ブラウザアクセス

では早速ちゃんと変換されるか確認してみましょう。

通常のサイズのアクセスは以下のようになります。

f:id:matsumoto_r:20151215222126p:plain

さらに、画像変換すべくクエリーパラメータを与えて、今回は半分のサイズになるように?type=halfとなるようにしてアクセスると、以下のように変換された結果が帰ってきました。

f:id:matsumoto_r:20151215222137p:plain

できました。

ベンチマーク

せっかくなので、ついでにベンチマークもとってみましょう。HTTP/2 + TLSなのでh2loadを使います。基本パラメータは以下とします。

h2load -t 4 -c 100 -m 100 -n 1000000 https://127.0.0.1:8080/
  • 変換前の画像へのベンチマーク
$ h2load -t 4 -c 100 -m 100 -n 1000000 https://127.0.0.1:8080/logo.png
starting benchmark...
spawning thread #0: 25 total client(s). 250000 total requests
spawning thread #1: 25 total client(s). 250000 total requests
spawning thread #2: 25 total client(s). 250000 total requests
spawning thread #3: 25 total client(s). 250000 total requests
TLS Protocol: TLSv1.2
Cipher: ECDHE-RSA-AES128-GCM-SHA256
Application protocol: h2
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 12.45s, 80349.06 req/s, 655.39MB/s
requests: 1000000 total, 1000000 started, 1000000 done, 1000000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 1000000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 8553033172 bytes total, 10030172 bytes headers (space savings 91.85%), 8525000000 bytes data
                     min         max         mean         sd        +/- sd
time for request:     3.45ms    330.42ms    111.99ms     46.70ms    62.28%
time for connect:    14.57ms    147.67ms     76.94ms     22.21ms    75.00%
time to 1st byte:    25.56ms    202.79ms    135.98ms     41.96ms    76.00%
req/s (client)  :     803.61     1766.93      935.77      291.06    89.00%
  • 変換された画像へのベンチマーク
 h2load -t 4 -c 100 -m 100 -n 1000000 https://127.0.0.1:8080/logo.png?type=half
starting benchmark...
spawning thread #0: 25 total client(s). 250000 total requests
spawning thread #1: 25 total client(s). 250000 total requests
spawning thread #2: 25 total client(s). 250000 total requests
spawning thread #3: 25 total client(s). 250000 total requests
TLS Protocol: TLSv1.2
Cipher: ECDHE-RSA-AES128-GCM-SHA256
Application protocol: h2
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 14.15s, 70663.46 req/s, 385.74MB/s
requests: 1000000 total, 1000000 started, 1000000 done, 1000000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 1000000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 5724037565 bytes total, 10034565 bytes headers (space savings 91.84%), 5696000000 bytes data
                     min         max         mean         sd        +/- sd
time for request:     8.55ms    332.16ms    129.32ms     42.88ms    65.24%
time for connect:    38.10ms    214.01ms     79.91ms     33.53ms    94.00%
time to 1st byte:    59.78ms    270.10ms    143.91ms     45.15ms    73.00%
req/s (client)  :     706.85     1201.34      793.84      171.11    85.00%

VMの環境でこれぐらいでればそこそこ良さそうですね。

logo.png logo.png?type=half
req/s 80349.06 70663.46

参考:index.htmlのような小さなファイルのベンチマーク

trusterd + TLS + HTTP/2は、小さなファイルの場合はどれぐらい性能がでるのかもついでに確認しておきます。

$ h2load -t 4 -c 100 -m 100 -n 1000000 https://127.0.0.1:8080/index.html
starting benchmark...
spawning thread #0: 25 total client(s). 250000 total requests
spawning thread #1: 25 total client(s). 250000 total requests
spawning thread #2: 25 total client(s). 250000 total requests
spawning thread #3: 25 total client(s). 250000 total requests
TLS Protocol: TLSv1.2
Cipher: ECDHE-RSA-AES128-GCM-SHA256
Application protocol: h2
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.40s, 294449.34 req/s, 13.76MB/s
requests: 1000000 total, 1000000 started, 1000000 done, 1000000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 1000000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 49014749 bytes total, 9011749 bytes headers (space savings 92.55%), 22000000 bytes data
                     min         max         mean         sd        +/- sd
time for request:      487us    125.01ms     28.70ms     14.40ms    70.64%
time for connect:    34.69ms    109.32ms     77.21ms     19.47ms    55.00%
time to 1st byte:    57.57ms    158.23ms    103.08ms     28.21ms    60.00%
req/s (client)  :    2947.22     5091.48     3476.10      646.64    84.00%

小さいファイルは、TLSかつfdのキャッシュ無しで 294449.34 req/s ぐらいでます。おお、まぁまぁはやい。

ついでにnginxのTLS+HTTP/2もベンチマーク

また、上記のベンチマーク結果だけでは基準となる値がどの程度かわかりにくいので、今回はさらにnginxもHTTP/2に対応している事を踏まえて、v1.9.6で以下のような設定をした上で、参考程度にindex.htmlとlogo.pngへのベンチマークを計測しました。

設定は以下のようにしており、この設定の場合はこの値だという参考情報としてお考え下さい。もっとチューニングしたら良い値が出るとは思いますので各々でお試し下さい。

  • nginx.conf
worker_processes auto;

events {
    worker_connections 10240;
    accept_mutex_delay 100ms;
}

daemon off;

http {
    include       mime.types;

    access_log off;
    server {
        listen  8080 ssl http2;
        ssl_ciphers AESGCM:HIGH:!aNULL:!MD5;
        server_name  localhost;

        root /usr/local/trusterd/htdocs;
        ssl_certificate /usr/local/trusterd/ssl/server.crt;
        ssl_certificate_key /usr/local/trusterd/ssl/server.key;
        ssl_buffer_size 4k;
    }
}
  • nginx + TLS + HTTP/2でindex.htmlに同一ベンチマークパラメータでアクセス
$ h2load -t 4 -c 100 -m 100 -n 1000000 https://127.0.0.1:8080/index.html
starting benchmark...
spawning thread #0: 25 total client(s). 250000 total requests
spawning thread #1: 25 total client(s). 250000 total requests
spawning thread #2: 25 total client(s). 250000 total requests
spawning thread #3: 25 total client(s). 250000 total requests
TLS Protocol: TLSv1.2
Cipher: ECDHE-RSA-AES128-GCM-SHA256
Application protocol: h2
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 10.24s, 97662.68 req/s, 16.02MB/s
requests: 1000000 total, 1000000 started, 1000000 done, 1000000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 1000000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 172004900 bytes total, 132000000 bytes headers (space savings 24.14%), 22000000 bytes data
                     min         max         mean         sd        +/- sd
time for request:      142us    297.97ms     79.58ms     44.96ms    71.00%
time for connect:    35.99ms    770.81ms    170.07ms    166.40ms    85.00%
time to 1st byte:    38.84ms    938.26ms    209.14ms    188.24ms    85.00%
req/s (client)  :     977.32     1552.77     1273.85      233.02    43.00%
  • nginx + TLS + HTTP/2でlogo.pngへのアクセス
$ h2load -t 4 -c 100 -m 100 -n 1000000 https://127.0.0.1:8080/logo.png
starting benchmark...
spawning thread #0: 25 total client(s). 250000 total requests
spawning thread #1: 25 total client(s). 250000 total requests
spawning thread #2: 25 total client(s). 250000 total requests
spawning thread #3: 25 total client(s). 250000 total requests
TLS Protocol: TLSv1.2
Cipher: ECDHE-RSA-AES128-GCM-SHA256
Application protocol: h2
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 15.92s, 62829.21 req/s, 520.57MB/s
requests: 1000000 total, 1000000 started, 1000000 done, 1000000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 1000000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 8688004900 bytes total, 136000000 bytes headers (space savings 23.60%), 8525000000 bytes data
                     min         max         mean         sd        +/- sd
time for request:      206us    285.12ms    137.15ms     48.74ms    70.04%
time for connect:    19.46ms    923.68ms    227.51ms    201.02ms    84.00%
time to 1st byte:    30.66ms       1.16s    286.52ms    229.84ms    77.00%
req/s (client)  :     628.66      869.51      721.25       71.74    50.00%

というような値になりました。まとめで表にします。

まとめ

ということで、画像変換をTLS + HTTP/2なサーバとして実装した上で、それに対するベンチマークにより、画像変換サーバとしてもそれなりにはやいということがわかりました。

今回用いたコンテンツのファイルサイズは以下です。

-rw-rw-r-- 1 matsumotory matsumotory   22 Dec 13 19:18 index.html
-rw-rw-r-- 1 matsumotory matsumotory 8.4K Dec 13 19:40 logo.png

ベンチマークパラメータは以下のとおりです。

h2load -t 4 -c 100 -m 100 -n 1000000 https://127.0.0.1:8080/

HTTP/2 + TLSの画像変換のベンチマークと参考のためにnginxでも計測した結果のまとめは、以下(req/sec)となりました。

index.html(22byte) logo.png(8.4kb) logo.png?type=half(5.6kb)
trusterd 294449.34 80349.06 70663.46
nginx 97662.68 62829.21 -

ということで、trusterdを使えば簡単にmrubyで画像変換サーバみたいなものがサクっと作れてそこそこ速いねという話でした。かなり遊べるサーバなので、是非Rubyで設定を書いて色々とお試し下さい。

今後、fd cacheの機能を実装すると、ベンチマーク的にはこれの2倍の性能が出ることはtrusterdのfd cacheのプロトタイプ実装で確認しているので、おいおい実装したいと思っております。