Hateburo: kazeburo hatenablog

Operations Engineer / Site Reliability / 運用系小姑 / Perl Monger

picohttpparserのRubyバインディングとPreforkサーバを書く時に便利なgemをリリースしたので、Rackサーバ書いてみた

GazelleでやったことをRubyでもやってみようと思い、まず picohttpparserRuby バインディングと、perforkなサーバを書く時に便利なモジュールであるParallel::PreforkRuby版を書いてリリースしました。

pico_http_parser

http://rubygems.org/gems/pico_http_parser

prefork_engine

http://rubygems.org/gems/prefork_engine

そしてこの2つを使って、StarletのRuby版を書いてみました。ソースコードはprefork_engineのrepositoryにあります。

https://github.com/kazeburo/prefork_engine/blob/master/example/starlet.rb

動かし方

まず上の2つのgemをいれます。bundlerを使っても良いですね

$ gem install pico_http_parser prefork_engine

prefork_engineのrepositoryをcloneしてきてexampleディレクトリに移動

$ git clone https://github.com/kazeburo/prefork_engine.git
$ cd prefork_engine/example

hello worldなサンプルアプリケーションも同梱されているので、そいつを動かしてみます

$ rackup -r ./starlet.rb -s Starlet -O MaxWorkers=5 -O MaxRequestPerChild=1000 config.ru

curlでアクセスすると

$ curl -sv http://localhost:9292/|less
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 9292 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.38.0
> Host: localhost:9292
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Connection: close
< Content-Type: text/html
< Date: Thu, 11 Dec 2014 07:00:30 GMT
< Server: RubyStarlet
<
{ [data not shown]
* Closing connection 0
hello world

ちゃんと出て来た。大丈夫そう!

軽くベンチマーク

環境はEC2のc3.4xlarge、Amazon Linuxを使いました。nginxのうしろにアプリケーションサーバとして動作させた場合を想定してます。

ベンチマーク準備

Rubyはxbuildを使っていれます。unix domain socketで通信する為にstart_serverというPerl製のコマンドが必要になるのでPerlもいれます。

$ git clone https://github.com/tagomoris/xbuild.git
$ ./xbuild/ruby-install 2.1.5 ~/local/ruby-2.1
$ xbuild/perl-install 5.20.1 ~/local/perl-5.20 -j4
$ export PATH=/home/ec2-user/local/ruby-2.1/bin:$PATH
$ export PATH=/home/ec2-user/local/perl-5.20/bin:$PATH

nginxはyumでインストールし、次のように設定をしました

worker_processes  4;

events {
  worker_connections  10000;
}

http {
  include     mime.types;
  access_log  off;
  sendfile    on;
  tcp_nopush  on;
  tcp_nodelay on;
  etag        off;
  upstream app {
    server unix:/dev/shm/app.sock;
  }

  server {
    location / {
      proxy_pass http://app;
    }
  }
}

比較のためにunicornもいれました。

$ gem install pico_http_parser prefork_engine unicorn

ruby版Starletの起動コマンド

$ start_server --path /dev/shm/app.sock -- rackup -r ./starlet.rb -E production -s Starlet -O MaxWorkers=12 -O MaxRequestPerChild=500000 config.ru

unicornのconfigと起動コマンド

$ cat unicorn.rb
worker_processes 8
preload_app true
listen "/dev/shm/app.sock"
$ unicorn -E production -c unicorn.rb config.ru

ベンチマーク結果

ruby版Starlet

$ ./wrk -t 2 -c 32 -d 30  http://localhost/
Running 30s test @ http://localhost/
  2 threads and 32 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   401.75us  114.12us   5.67ms   80.92%
    Req/Sec    40.83k     2.90k   53.56k    72.18%
  2291892 requests in 30.00s, 384.58MB read
Requests/sec:  76397.24
Transfer/sec:     12.82MB

unicorn

$ ./wrk -t 2 -c 32 -d 30  http://localhost/
Running 30s test @ http://localhost/
  2 threads and 32 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   393.75us  188.08us   4.41ms   78.60%
    Req/Sec    42.66k     6.64k   62.33k    71.41%
  2389390 requests in 30.00s, 437.40MB read
Requests/sec:  79647.31
Transfer/sec:     14.58MB

picohttpparserがすごく速いというのはありますが、それ以外の部分がRubyだけで書かれているにしては良いベンチマーク結果になっている気がします