LoginSignup
108
68

More than 5 years have passed since last update.

Pumaの本当の力を引き出す

Posted at

rails5からは標準のWebサーバがPumaになって、Unicornの代わりにPumaを使う人が増えてきました。
ただせっかくPumaを使うのであれば、Unicornとの違いを意識して使いたい
というわけで、ありがちではありますが、PumaとUnicornの比較をしてみたいと思います。

マルチプロセスとマルチスレッド

Unicornはマルチプロセス
Pumaはマルチスレッドで動きます

Unicornはプロセスごとに通信処理をおこなうので、重たい通信があった場合そこでブロックされて全体が重くなってしまいます。
これをさばけるようにするにはUnicornのworker数を増やす必要があるのですが、CPUのコアの数による処理制限があるためむやみには増やせません。

Pumaはマルチスレッドで動くので、プロセスを増やす必要がないため、リソースが少ない中でも効率的にリクエストをさばくことが可能になります。

プロセスとスレッドの正しい違いについてはこちらを御覧ください!
https://moro-archive.hatenablog.com/entry/2014/09/11/013520

rubyの処理系による違い

Pumaはマルチスレッドで動くことはわかりました。
ただここで問題があります。

現在の実装では Ruby VM は Giant VM lock (GVL) を有しており、同時に実行される ネイティブスレッドは常にひとつです。 ただし、IO 関連のブロックする可能性があるシステムコールを行う場合には GVL を解放します。
https://docs.ruby-lang.org/ja/2.5.0/doc/spec=2fthread.html

つまりデフォルトではスレッドは1つ、CPUのI/O待ちの時のみ複数スレッドになる、とのこと。
意味はなくはないけど、Pumaの力を存分に発揮しているわけではないのです。

Ruby と JRuby

Rubyの処理系による違いによって、上記のようなスレッドの扱いの違いがあります。
処理系とは JRubyとかmrubyとか呼ばれているものたち。
https://qiita.com/takeyuweb/items/ea7b42746152f03efdaa

普段何も意識せずに使っているのはCRubyです。

ここでPumaのリポジトリを覗いてみましょう。
https://github.com/puma/puma

Since each request is served in a separate thread, truly concurrent Ruby implementations (JRuby, Rubinius) will use all available CPU cores.

JRubyやRubiniusのような処理系を使えば、PumaでCPUパワーを存分に引き出せるよって言ってます。
そこで今回はJRubyを使って検証してみることにしました。

JRubyは JavaVM上で動くRubyのことです。
JRubyはGVLの影響を受けずに、マルチスレッドができるのです。

パフォーマンス比較してみる

Pumaの力を引き出す方法がわかったところで

  • Ruby + Unicorn
  • JRuby + Puma

この2つでパフォーマンス比較してみます。
パフォーマンス計測には k6 というコマンドつかってみます。
https://qiita.com/szk3/items/c1172ef3d182d7fe6868

railsアプリケーションでページを表示させるだけです

app/controllers/top_controller.rb
class TopController < ApplicationController
  def index
    sample_array = []
    input_txt = ""
    100000.times do |i|
      sample_array << i
    end
    sample_array.each do |i|
      input_txt += i.to_s + "\n"
    end
    File.open( "./tmp/test/" + SecureRandom.hex(8) + ".txt", 'w') do |file|
      file.puts(input_txt)
    end
  end
end
app/views/top/index.erb
<h1>ほげ</h1>

結果発表!!

worker数: 2
スレッド数: 5
で共通です。

AWSインスタンス: c4.large

構成 ec2 vus iterations 結果
cruby + unicorn c4.large 5 25 avg=28.51s
min=13.62s
med=29.06s
max=36.98s
jruby + puma c4.large 5 25 avg=37.94s
min=35.87s
med=37.93s
max=40.14s

JRubyのほうが遅くなってしまう結果になりました。
はっきり原因はわかりませんが、サーバ負荷(CPU使用率)はかなり高かったので、処理しきれない状態でもスレッド処理によってリクエスト数が増え逆に重くなった的な...?

AWSインスタンス: c5.xlarge

構成 ec2 vus iterations 結果
cruby + unicorn c5.xlarge 5 25 avg=18.19s
min=7.14s
med=16.99s
max=25.12s
jruby + puma c5.xlarge 5 25 avg=18.12s
min=15.76s
med=18.28s
max=20.15s

JRuby + Pumaのパフォーマンスが結構よくなってきました。
ただこのときもサーバ負荷はかなり高くなっていたので、 c4.large でのテストと一緒の現象で遅くなってしまっていたかもしれません。

AWSインスタンス: c5.2xlarge

構成 ec2 vus iterations 結果
cruby + unicorn c5.xlarge 5 25 avg=17.83s
min=7.03s
med=16.44s
max=24.67s
jruby + puma c5.xlarge 5 25 avg=9.86s
min=8.43s
med=9.56s
max=11.43s
cruby + puma c5.xlarge 5 25 avg=35.34s
min=33.7s
med=34.35s
max=37.49s

だいぶJRuby + Pumaのパフォーマンスがあがってきました。
CPUパワーが結構キモになりそうです。

ここだけ CRuby + Pumaの構成のパフォーマンス確認をしてみましたが、パフォーマンスが結構悪くなってしまいました。
ただUnicornと比べてこんなに差がでるかな...?

おまけでworker数を4にして cruby + unicornで計測してみました

構成 ec2 vus iterations 結果
cruby + unicorn c5.xlarge 5 25 avg=9.96s
min=7.12s
med=8.52s
max=16.55s

だいぶ早くなりました。

パフォーマンス計測まとめ

ある程度負荷のある大量アクセスをさばく場面でPumaを使うのであれば、Rubyの処理系に関しても少し意識をする必要があるかなと思いました。
場合によってはUnicornを普通に使ったほうがパフォーマンスが良い場合があるので、注意です。

108
68
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
108
68