Herokuのダッシュボードには、次の画像のように、稼働しているアプリのリソース使用状況をMetricsとして表示する機能がある。
最近某アプリで、このMemoryのところが常に上限を超えている状況が発生したのだけど、原因は意外に気づかないところだったので、ブログに書いておく。
ふつーの人は、
Getting Started with Rails 4.x on Heroku | Heroku Dev Center
とかを見ながら、config/unicorn.rbをコピペしてしまうと思う(自分もそうだった)。
worker_processes Integer(ENV["WEB_CONCURRENCY"] || 3) timeout 15 preload_app true before_fork do |server, worker| Signal.trap 'TERM' do puts 'Unicorn master intercepting TERM and sending myself QUIT instead' Process.kill 'QUIT', Process.pid end defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect! end after_fork do |server, worker| Signal.trap 'TERM' do puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to send QUIT' end defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection end
この
worker_processes Integer(ENV["WEB_CONCURRENCY"] || 3)
の部分がポイントで、これでunicorn.rbのワーカープロセスの数が決まる。
環境変数WEB_CONCURRENCY
を設定していればその数が、設定していなければ3がunicornのワーカープロセスの数になる。
これが僕がハマった罠だった。
Herokuの1Xのdynoは、メモリの最大値が500Mである。自分のアプリはWebアプリとしてはごく普通のアプリだと思うが、unicornのワーカープロセス1つのメモリ使用量の平均値は300Mであった(これはNewRelicで計測)。
したがって、何も考えずにunicorn.rbの設定ファイルをパクっちゃうと、環境変数がなければ、unicornのワーカープロセスの数が3つになってしまい、平均300 x 3 = 900Mもメモリを使用してしまう。これは1Xのdynoの最大値を大きく超えている。
自分は、これに気づかずに2Xのdynoを購入してしまったが、1Xですませるなら、環境変数WEB_CONCURRENCY
を1に設定しておくべきであった。
ちなみに、2Xにするとdynoのメモリの最大値は1Gまで上がるが、これでも900Mが平均だと結構な頻度でswapが発生し、メモリ使用量のエラー(R14)が出てしまっていた。2Xの場合、WEB_CONCURRENCY
を2に設定したら、R14エラーは出なくなった。某アプリでは1Xには戻さずに、並列処理ができるようにするために結局2Xで稼働している。