Rails5で標準採用されたPumaについての記事。
Unicornとの対比しつつ、process/thread 、GIL、スロークライアント問題について記載されています。
どれも知っておいて損はない話です。
後述していますが、Puma採用時にライブラリがスレッドセーフかどうかを確認しておかないといけないというのは、結構きついかもしれないですね(実際のところ気にしないといけないケースは少ないのでしょうが)。
以下、気になったところ。
GIL(Global Interpreter Lock)
プロセスごとにロックがかかる仕組み。
threads = Array.new(3) do Thread.new do rand(10) end end threads.each(&:value)
MRIだとrand(10)
のところは、GILでロックされているので、1つのスレッドしか実行できない。
ただし、I/O であればGILのロックは解放される。
require "open-uri" threads = Array.new(3) do Thread.new do open("http://example.com") end end threads.each(&:value)
MRIは、JRubyやRubiniusに比べてthreadによる効果は小さい。
GILを採用している理由は、MRI及びC拡張ライブラリがスレッドセーフでないこと、MRIをスレッドセーフにするとシングルスレッド利用時の性能が落ちることなど。
Railsでの標準採用の理由
- Heroku スロークライアント問題の緩和のためPumaが採用された
- WEBRickはAction Cableで利用するhijack APIに未対応。
1プロセスでWeb用とWebSocket用のスレッドを取り扱うことができる。
Connection Pool数
RailsのConnection Pool数は、Pumaのスレッド数に合わせる必要があります。
RailsのConnection Poolについてはこちらが詳しいです。
Rails4.2のコネクションプールの実装を理解する - Akatsuki Hackers Lab | 株式会社アカツキ(Akatsuki Inc.)
1スレッドあたり1コネクションしか使いません。 つまり、シングルスレッドのUnicornでは、1ワーカープロセス = 1コネクションとなります。
プロセス数とスレッド数
Pumaのドキュメントによると、ワーカープロセス数は、MRIならCPUコア数の1.5倍。
スレッド数は同時接続数をワーカープロセス数で割った値、Pumaはデフォルト16に設定されていて、まともな数字であると記載されています。
puma/DEPLOYMENT.md at master · puma/puma
Use cluster mode and set the number of workers to 1.5x the number of cpu cores in the machine, minimum 2. Set the number of threads to desired concurrent requests / number of workers. Puma defaults to 16 and that's a decent number.
Puma採用時に注意すべき点
どれも使用頻度は低いでしょうが、Rails5で追加された対応方法もあるようです。
でも、ライブラリがスレッドセーフかどうかを見ないといけないんですね。
これはちょっと大変だなぁ。
対応方法
スレッドローカル変数
# before class Content class << self attr_accessor :adult end end # after class Content class << self def adult=(boolean) Thread.current[:adult] = boolean end def adult Thread.current[:adult] end end end
thread_cattr_accessor
Rails5以降で利用可。
# after class Content thread_cattr_accessor :adult end