読者です 読者をやめる 読者になる 読者になる

WEB+DB PRESS Vol.94 Pumaについて

WEB+DB PRESS Vol.94

WEB+DB PRESS Vol.94

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