wrkに無理矢理なpatchをあてて、unix domain socket経由でHTTPサーバをベンチマークできるようにしてみました。

pullreqはしてない。

GazelleやRhebokといったアプリケーションサーバを作っていますが、TCP経由のベンチマークではEphemeral Portの枯渇やTIME WAITの上限にあたってしまい、ベンチマークがしづらいという問題があります。

そこでnginxをreverse proxyとして設置し、nginxとアプリケーションサーバ間をunix domain socketで繋いでベンチマークをとっていましたが、nginxがボトルネックになりやすく、直接アクセスしたいなと考えていたので、やってみました。

これを使ってRhebokとUnicornの “Hello World” ベンチマークを行ったところ、unix domain socketのベンチマーク結果はTCPに比べて、Rhebokで5倍、Unicornで3倍高速という数字がでました。

rhebok_tcp_unix.png

Rhebokは最新版でHTTP/1.1のサーバとなり、デフォルトでTransfer-Encoding: chunkedを使います。Unicornはchunked transferを行わないので、機能的にあわせるためrack middlewareを導入しているのが上記のグラフの「Unicorn + Chunked」です。

このベンチマーク結果をみると、やはりTCPの新規コネクションのコストは大きく、TCPの都度接続は避け、できるだけコネクションを使い回すか、unix domain socketの使用したいと思うでしょう。

ただ、Reverse Proxyとアプリケーションサーバ、もしくはクライアントとアプリケーションサーバ間のTCP接続をkeepaliveする場合、保持すべき接続数のチューニングが難しくなりがちなので、自分としてはお勧めしません。クライアントとのTCP接続の維持をReverse Proxyで行い、アプリケーションサーバ間との接続はunix domain socketを使うのがよいのでしょう。もちろん、両者が同じホスト上で動作していないと使えませんが。。

使い方

まずブランチ指定してcloneしてきて、make

$ git clone -b unixdomain https://github.com/kazeburo/wrk.git
$ cd wrk
$ make

-P オプションでパスを指定します。

$ ./wrk -t 1 -c 30 -d 30 -P /tmp/app.sock http://localhost/benchmark

簡単ですね

RhebokとUnicornのベンチマーク

ベンチマークはEC2のc3.4xlargeを使いました。コア数は16です。OSはAmazon Linuxです。

以下のカーネルチューニングをしました

$ sudo sysctl -w net.core.netdev_max_backlog=8192
$ sudo sysctl -w net.core.somaxconn=32768
$ sudo sysctl -w net.ipv4.tcp_tw_recycle=1

一番下はTCPのポート枯渇対策です。ただTCPのベンチマークはどれもtimeoutのエラーが出てしまっています。

Rubyはxbuildを使って2.1.0をインストール

$git clone https://github.com/tagomoris/xbuild.git
$ ./xbuild/ruby-install 2.1.5 ~/local/ruby-2.1
$ export PATH=/home/ec2-user/local/ruby-2.1/bin:$PATH
$ gem install rhebok unicorn

アプリケーションサーバのワーカー数は8とし、wrkはスレッド6個、同時接続数600で実行しました。

アプリケーションはconfig.ruに直接書いている

$ cat config.ru
class HelloApp
  def call(env)
    [
      200,
      {},
      ["hello world\n"]
    ]
  end
end
run HelloApp.new
# run Rack::Chunked.new(HelloApp.new)

unicornの設定はこちら

$ cat unicorn.rb 
worker_processes 8
preload_app true
#listen "/dev/shm/app.sock", :backlog=>16384
listen 8080, :backlog=>36384

Rhebok + unix domain socket

$ rackup -s Rhebok -O Path=/dev/shm/app.sock -O BackLog=16384 -E production -O MaxRequestPerChild=0 -O MaxWorkers=8 config.ru
$ ./wrk -t 6 -c 600 -d 30 --timeout 60 -P /dev/shm/app.sock http://localhost:8080/
Running 30s test @ http://localhost:8080/
  6 threads and 600 connections
  Thread Stats   Avg      Stdev  Max   +/- Stdev
    Latency  1.73ms  244.96us   6.62ms   81.28%
    Req/Sec 57.64k   4.04k   66.56k 69.06%
  9762337 requests in 30.00s, 1.28GB read
Requests/sec: 325426.21
Transfer/sec:    43.76MB

Rhebok + TCP

$ rackup -s Rhebok -O Port=8080 -O BackLog=16384 -E production -O MaxRequestPerChild=0 -O MaxWorkers=8 config.ru
$ ./wrk -t 6 -c 600 -d 30 --timeout 60 http://localhost:8080/
Running 30s test @ http://localhost:8080/
  6 threads and 600 connections
  Thread Stats   Avg      Stdev  Max   +/- Stdev
    Latency 36.02ms  194.43ms   1.83s   97.66%
    Req/Sec 11.87k   2.20k   21.89k 89.91%
  2021671 requests in 30.00s, 271.85MB read
  Socket errors: connect 0, read 0, write 0, timeout 56
Requests/sec:  67392.13
Transfer/sec:     9.06MB

Unicorn + unix domain socket

$ unicorn -c unicorn.rb -E production config.ru TCP
$ ./wrk -t 6 -c 600 -d 30 --timeout 60 -P /dev/shm/app.sock http://localhost:8080/
Running 30s test @ http://localhost:8080/
  6 threads and 600 connections
  Thread Stats   Avg      Stdev  Max   +/- Stdev
    Latency  3.21ms  170.17us  11.49ms   88.06%
    Req/Sec 32.71k   2.04k   43.67k 65.95%
  5534941 requests in 30.00s, 543.69MB read
Requests/sec: 184506.07
Transfer/sec:    18.12MB

Unicorn + TCP

$ unicorn -c unicorn.rb -E production config.ru TCP
$ ./wrk -t 6 -c 600 -d 30 --timeout 60 http://localhost:8080/
Running 30s test @ http://localhost:8080/
  6 threads and 600 connections
  Thread Stats   Avg      Stdev  Max   +/- Stdev
    Latency 33.58ms  179.94ms   1.75s   97.80%
    Req/Sec 11.01k   2.04k   22.00k 89.73%
  1873600 requests in 30.00s, 184.04MB read
  Socket errors: connect 0, read 0, write 0, timeout 49
Requests/sec:  62455.78
Transfer/sec:     6.13MB

Unicorn + chunked + unix domain socket

$ unicorn -c unicorn.rb -E production chunked.ru unix
$ ./wrk -t 6 -c 600 -d 30 --timeout 60 -P /dev/shm/app.sock http://localhost:8080/
Running 30s test @ http://localhost:8080/
  6 threads and 600 connections
  Thread Stats   Avg      Stdev  Max   +/- Stdev
    Latency  5.24ms  265.60us  13.67ms   77.33%
    Req/Sec 19.76k   1.79k   26.00k 68.71%
  3404283 requests in 30.00s, 457.77MB read
Requests/sec: 113481.40
Transfer/sec:    15.26MB

Unicorn + chunked + TCP

$ unicorn -c unicorn.rb -E production chunked.ru tcp
$ ./wrk -t 6 -c 600 -d 30 --timeout 60 http://localhost:8080/
Running 30s test @ http://localhost:8080/
  6 threads and 600 connections
  Thread Stats   Avg      Stdev  Max   +/- Stdev
    Latency   555.60ms  1.10s   3.43s   81.56%
    Req/Sec  7.85k   5.05k   26.89k 65.28%
  1339932 requests in 30.00s, 180.18MB read
  Socket errors: connect 0, read 0, write 0, timeout 29
Requests/sec:  44659.91
Transfer/sec:     6.01MB

HTTP/1.1に対応したRhebok、Gazelleと共にお試しくださいませ。

このブログ記事について

このページは、Masahiro Naganoが2015年1月28日 01:38に書いたブログ記事です。

ひとつ前のブログ記事は「h2o と server_starter で graceful restart with Docker」です。

次のブログ記事は「株式会社メルカリに入社しました」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

ウェブページ

OpenID対応しています OpenIDについて
Powered by Movable Type 4.27-ja