仕事でRailsを使うことになり、APサーバの選定にあたってPuma, Unicorn, Passenger の比較検討を行いました。方法としてはJMeterでAPサーバにデプロイしたRailsアプリケーションに対して負荷をかけられるだけかけるというやり方です。
試験環境
試験の環境としては下記の構成です。
- Ruby2.0, Rails4
- アプリケーションサーバ:1台(VM)
- JMeterサーバ:3台(VM)
- JMeterクライアント:1台(通常の作業PC)
サーバ構成 hostaname CPU 仮想コア数(Per CPU) Memory Disk 用途 loadtest01 2 4 8192MB 20GB APサーバ loadtest02 1 1 4096MB 20GB JMeterサーバ loadtest03 1 1 4096MB 20GB JMeterサーバ loadtest04 1 1 4096MB 20GB JMeterサーバ
サーバアーキテクチャの比較
各APサーバのアーキテクチャの比較は下記の通りです。
Puma | Unicorn | Passenger | |
---|---|---|---|
デプロイモデル | Reverse Proxy | Reverse Proxy | Nginx Module |
Process/Th | Multi Processes / Multi Threads | Multi Processes / Single Thread | Multi Processes / Single Thread(OSS Edition) |
Multi Processes / Multi Threads(Commercial | |||
その他特徴 | Workerプロセスごとにスレッドを立ち上げる | Pull型(Workerプロセス側からキューのタスクを取得しにいく) | トラフィックに応じてプロセス数を自動調整。商用Editionのみマルチスレッド利用可能 |
処理内容
処理内容としては、純粋にAPサーバの処理能力の比較にするため、DBアクセス等はせず、フィボナッチ数列を計算して返す処理を行います。ロジックは書きサイトを参考にしました。
1 class StaticPagesControlle r < Applicatio nControlle r 2 3 def fibonacci 4 @fibonacci = calc_fibon acci(params[:n].to_i) 5 end 6 7 private 8 9 def calc_fibon acci(n) 10 if (n < 2) 11 return n; 12 else 13 p2 = 0; 14 p1 = 1; 15 2.upto(n) { p2, p1 = p1, p2 + p1 } 16 return p1; 17 end 18 end 19 20 end
JMeterシナリオ
JMeterのシナリオとしては、
- JMeterスレッド数:100
- Ramp up:0
- ループ数:無限ループ
- 継続時間:5分間
という内容です。
JMeterのシナリオファイルはこちらから取得できます。
APサーバごとの設定値による差の検証
まずは各APサーバごとに、設定値の変更による差を検証しました。
Puma
ワーカプロセス数 | スレッド数 | サンプル数 | 平均値(msec) | 中間値(msec) | 90%LINE (msec) | 最小値(msec) | 最大値(msec) | スループット(/sec) | KB/sec | エラー数 |
---|---|---|---|---|---|---|---|---|---|---|
1 | 500 | 43079 | 2090 | 1978 | 2379 | 129 | 2519 | 141.68 | 414.67 | 多発 |
100 | 8 | 241811 | 365 | 57 | 1071 | 6 | 46414 | 799.43 | 2339.49 | 0 |
100 | 32 | 243620 | 362 | 60 | 1136 | 6 | 16890 | 805.37 | 2356.83 | 32 |
64 | 32 | 242825 | 365 | 51 | 1355 | 7 | 103157 | 802.35 | 2347.94 | 1 |
32 | 32 | 241934 | 366 | 46 | 760 | 6 | 252504 | 798.24 | 2335.89 | 0 |
16 | 64 | 237270 | 373 | 31 | 339 | 6 | 298546 | 782.00 | 2288.34 | 0 |
16 | 96 | 231996 | 382 | 37 | 644 | 6 | 241748 | 762.49 | 2231.26 | 0 |
- ワーカプロセス数1のケースでは全くCPUを使い切れていませんが、他のケースではCPUを使い切った状態で動いていました。
- スレッド数を増加させてもリソースの使用量はあまり変化しませんでしたが、プロセス数を増やすと使用量がかなり増えます。
- メモリに余裕があってもCPUが振り切る方が早いです。
Unicorn
ワーカプロセス数 | サンプル数 | 平均値(msec) | 中間値(msec) | 90%LINE (msec) | 最小値(msec) | 最大値(msec) | スループット(/sec) | KB/sec | エラー数 |
---|---|---|---|---|---|---|---|---|---|
1 | 4294 | 11345 | 572 | 50553 | 77 | 176247 | 11.74 | 34.54 | 433 |
8 | 8686 | 6026 | 562 | 13721 | 77 | 141193 | 22.15 | 65.15 | 389 |
32 | 42943 | 1465 | 325 | 2844 | 51 | 183552 | 103.48 | 304.38 | 280 |
64 | 191254 | 440 | 170 | 375 | 26 | 115819 | 498.54 | 1466.46 | 48 |
96 | 247191 | 360 | 170 | 261 | 49 | 82454 | 773.58 | 2275.69 | 3 |
128 | 256050 | 343 | 252 | 452 | 13 | 20587 | 836.00 | 2459.82 | 0 |
160 | 254117 | 347 | 291 | 537 | 15 | 8825 | 835.54 | 2458.54 | 0 |
192 | 252158 | 349 | 302 | 588 | 13 | 5746 | 833.94 | 2453.87 | 0 |
224 | 229723 | 383 | 275 | 592 | 9 | 299799 | 697.12 | 2045.15 | 2 |
- ワーカプロセス数が1の場合は1CPUしか使われない
- ワーカプロセス数をコア数と同じ8にした場合でも、すべてのコアが使われるわけではない
- ワーカプロセス数をコア数の4倍の64まで増やすとかなりCPUが使われるようになってきて、128まで増やすとCPUはほぼ使い切られて、メモリの使用率も高くなる。
- ワーカプロセス数を192まで増やした時点でメモリもほぼ使い切られ、224まで増やすとメモリが足りなくなり、Swapが発生して遅くなる。
Passenger
プロセス数 | サンプル数 | 平均値(msec) | 中間値(msec) | 90%LINE (msec) | 最小値(msec) | 最大値(msec) | スループット(/sec) | KB/sec | エラー数 |
---|---|---|---|---|---|---|---|---|---|
1 | 65188 | 1376 | 1282 | 1325 | 1224 | 8690 | 215.15 | 646.07 | 0 |
8 | 245153 | 360 | 355 | 368 | 297 | 2441 | 810.57 | 2433.99 | 0 |
16 | 246737 | 357 | 353 | 381 | 279 | 2339 | 815.77 | 2448.77 | 0 |
24 | 241165 | 366 | 360 | 391 | 281 | 2414 | 797.27 | 2392.30 | 0 |
- プロセス数が1だと4コアしか使われていない
- プロセス数をコア数と同じにするとどのコアもかなり使われるようになる。メモリは余裕あり。
- プロセス数をコア数以上に増やしていってもCPU, メモリの使用率はあまり変化しない。CPUは80%まで使われることが多いが、アイドルが10%前後残っていて、使い切ることはない
各APサーバの最適値同士を比較
各APサーバの最適値同士を比較して、使用するAPサーバを決定する
APサーバ | サンプル数 | 平均値(msec) | 中間値(msec) | 90%LINE (msec) | 最小値(msec) | 最大値(msec) | スループット(/sec) | KB/sec |
---|---|---|---|---|---|---|---|---|
Puma | 241811 | 365 | 57 | 1071 | 6 | 46414 | 799.43 | 2339.49 |
Unicorn | 252158 | 349 | 302 | 588 | 13 | 5746 | 833.94 | 2453.87 |
Passenger | 246737 | 357 | 353 | 381 | 279 | 2339 | 815.77 | 2448.77 |
- Pumaはスループットが一番低いのに加えて最大値、90%LINEの遅さ、不安定さで候補から除外。
- Passengerは安定しているが最小値が他の2つに比べて大きい。90%LINE、最大値は優秀。
- Unicornはスループットが一番大きいのと、最小値、平均値も悪くない。最大値、90%LINEがPassengerと比 べると遅いのが懸念点。
APサーバ | サンプル数 | 平均値(msec) | 中間値(msec) | 90%LINE (msec) | 最小値(msec) | 最大値(msec) | スループット(/sec) | KB/sec |
---|---|---|---|---|---|---|---|---|
Unicorn | 23075 | 12 | 10 | 13 | 4 | 116 | 75.32 | 221.50 |
Passenger | 23092 | 11 | 9 | 12 | 5 | 118 | 75.38 | 226.67 |
- UnicornとPassengerでほぼ差はなし
- Passengerは最初の比較時に他と比べて最小値が遅いことが懸念だったが、実際の状況に近いアクセス数では 問題なく、Unicornが負荷が低い状態でもメモリリソースを消費するのに対してPassengerはリソースの消費が少ないこと から、Passengerを第一候補と考える。
検証時の問題点
検証時に発生した問題点を参考までに記載しておきます。
Passenger
- デフォルトの状態で負荷をかけたところ、下記のようなエラーが多発してHTTPステータスコード 503が返される。
1 [ 2013-11-2012:21:13.0 578 17419/7fbb bc9da700 Pool2/Grou p.h:331 ]: Request queue is full. Returning an error
リクエストのQueueがあふれたことによるもののようです。Queue のサイズのデフォルトは100なので、無制限にするために passenger_
Unicorn
- Nginxとの連携をUnixドメインソケットで行う設定で負荷をかけたところ、下記エラーが多発。
1 2013/11/2011:32:35 [error] 27462#0: *622742 connect() to unix:///ho me/test_us er/server- proto/unic orn.sock failed (11: Resource temporaril y unavailabl e) while connecting to upstream, client: 192.168.51 .234, server: loadtest01 .test.com, request: "GET /fibonacci ?n=1000 HTTP/1.1", upstream: "http://uni x:///home/ test_user/ server-pro to/unicorn .sock:/fib onacci?n=1000", host: "loadtest01 .test.com: 3090"
下記サイトなどを参考に、TCPポートによる連携に変更しました。
変更後に負荷をかけたところ、エラーは概ね解消しました。負荷をかけ続けていると、件数は少なくなったもののエラーが発生しました。HTTPステータスコードは 502 Bad Gateway
1 2013/11/2014:45:29 [error] 25979#0: *254606 upstream prematurel y closed connection while reading response header from upstream, client: 192.168.51 .232, server: loadtest01 .nubee.com , request: "GET /fibonacci ?n=1000 HTTP/1.1", upstream: "http://127 .0.0.1:307 0/fibonacc i?n=1000", host: "loadtest01 .test.com: 3090"
1 [2013-11-2014:44:09.1 09250] ERROR worker=114 PID:26737 timeout (31s > 30s), 2 killing 3 [2013-11-20 14:44:09.1 19595] ERROR reaped #<Process::S tatus: pid 26737 SIGKILL 4 (signal 9)> worker=114 5 [2013-11-20 14:44:10.1 40398] INFO worker=114 ready
検証のためにUnicorn側のタイムアウト設定を大幅に増やしました。30秒 → 300秒に変更。Nginxの設定にも下記を追加しました。
1 send_timeout 300; 2 proxy_conn ect_timeou t 300; 3 proxy_read _timeout 300;
これでひとまずエラーは回避できました。。スループットの検証のためにタイムアウト値を大きくしていますが、実際の環境ではタイムアウト値は小さく設定する必要があります。