patorashのブログ

方向性はまだない

Herokuでpuma_worker_killerを適切に設定する

昨年、こんな記事を書いていました。

patorash.hatenablog.com

これで設定できていたと思ったのですが、アクセスが集中したときにワーカーの再起動が起きず、スワップが発生してR14(メモリ関連のエラー)が頻発していました。このときに行った設定はどうも一定時間経過したらpumaのワーカーを再起動するという設定だけで、メモリ使用量が増えたらワーカーを再起動させる設定をしていなかったようでした(完全にミス…)。

puma_worker_killerの設定を行う

puma_worker_killerの設定はREADMEに書いてあったので、今回はそれをちゃんと行いました。

github.com

まず、 config/puma.rb に以下を追加しました。

before_fork do
  PumaWorkerKiller.config do |config|
    config.ram           = 1024 # 単位はMB。デフォルトは512MB
    config.frequency     = 10    # 単位は秒
    config.percent_usage = 0.90 # ramを90%以上を使用したらワーカー再起動
    config.rolling_restart_frequency = 6 * 3600 # 6時間
  end
  PumaWorkerKiller.start
end

その後、bin/rails sをしてみます。

動作確認

puma_worker_killerが起動していたら、以下のようなログが出ます。

$ bin/rails s
=> Booting Puma
=> Rails 5.2.3 application starting in development
=> Run `rails server -h` for more startup options
[37580] Puma starting in cluster mode...
[37580] * Version 4.0.1 (ruby 2.6.3-p62), codename: 4 Fast 4 Furious
[37580] * Min threads: 5, max threads: 5
[37580] * Environment: development
[37580] * Process workers: 2
[37580] * Preloading application
[37580] * Listening on tcp://localhost:3000
[37580] Use Ctrl-C to stop
[37580] - Worker 0 (pid: 37906) booted, phase: 0
[37580] - Worker 1 (pid: 37907) booted, phase: 0
[37580] PumaWorkerKiller: Consuming 764.96484375 mb with master and 2 workers.
[37580] PumaWorkerKiller: Consuming 764.96484375 mb with master and 2 workers.

PumaWorkerKillerが、pumaが使っているメモリの全体量をログに出します。これでいえば、約765MB使ってるということです。

ワーカーが再起動するかチェック

今度は敢えてたくさんのワーカーを起動させて、多くのメモリを使わせてみます。ワーカー数の設定は環境変数WEB_CONCURRENCYにしているので、今回はそれを3に指定してみます。

$ env WEB_CONCURRENCY=3 bin/rails s
=> Booting Puma
=> Rails 5.2.3 application starting in development
=> Run `rails server -h` for more startup options
[38214] Puma starting in cluster mode...
[38214] * Version 4.0.1 (ruby 2.6.3-p62), codename: 4 Fast 4 Furious
[38214] * Min threads: 5, max threads: 5
[38214] * Environment: development
[38214] * Process workers: 3
[38214] * Preloading application
[38214] * Listening on tcp://localhost:3000
[38214] Use Ctrl-C to stop
[38214] - Worker 0 (pid: 38523) booted, phase: 0
[38214] - Worker 1 (pid: 38524) booted, phase: 0
[38214] - Worker 2 (pid: 38525) booted, phase: 0
[38214] PumaWorkerKiller: Out of memory. 3 workers consuming total: 1010.109375 mb out of max: 921.6 mb. Sending TERM to pid 38525 consuming 252.52734375 mb.
[38214] - Worker 2 (pid: 38534) booted, phase: 0
[38214] PumaWorkerKiller: Out of memory. 3 workers consuming total: 1010.12109375 mb out of max: 921.6 mb. Sending TERM to pid 38534 consuming 252.53125 mb.
[38214] - Worker 2 (pid: 38543) booted, phase: 0

921MB以上を使用し(1010MB)、Out of memoryになったため、再起動させているのが確認できました。

Heroku環境ではどう設定すればいいか?

ここで私が気になったのは、config.ramの指定です。上記では1024と固定値にしており、プロダクション環境でもStandard-2X Dynoを使っているので(2X Dynoのメモリは1024MB)これでも問題ないのですが、もしかしたらお試しの環境を準備する際にStandard-1Xを使うこともあり得るかもしれません。もしくは、performance-mとかを使う可能性だってあり得ます。その時に、わざわざこの値を編集したくありません。

Dynoのメモリ量を取得する方法がないかと思い、bashで接続してfreeコマンドを打ってみたら、メモリが60GBと表示されたので(おそらくホストのメモリ量)、軽く絶望していたのですが、なんとなく環境変数を確認したところ、Dynoのメモリ量が取得できました!MEMORY_AVAILABLEで取得可能です。一応、試してみました。

Standard-1X Dyno

$ heroku run --size=Standard-1X bash --app hoge 
Running bash on ⬢ hoge... up, run.9315 (Standard-1X)
~ $ printenv
略…
MEMORY_AVAILABLE=512
略…

Standard-2X Dyno

$ heroku run --size=Standard-2X bash --app hoge 
Running bash on ⬢ hoge... up, run.6999 (Standard-2X)
~ $ printenv
略…
MEMORY_AVAILABLE=1024
略…

Performance-M Dyno

$ heroku run --size=Performance-M bash --app hoge 
Running bash on ⬢ hoge... up, run.2701 (Performance-M)
~ $ printenv
略…
MEMORY_AVAILABLE=2560
略…

puma_worker_killerに設定する

これらを元に設定します。puma_worker_killerのデフォルト値が512MBなので、そうしておきます。

before_fork do
  PumaWorkerKiller.config do |config|
    config.ram           = Integer(ENV.fetch('MEMORY_AVAILABLE', 512))
    # その他は省略
  end
  PumaWorkerKiller.start
end

これで、どのWeb Dynoにも対応したpuma_worker_killerの設定が完了しました🎉

ローカルでワーカーの再起動を起こさないようにする

このままだと、ローカルでrailsを起動すると、環境変数MEMORY_AVAILABLEがないため、512MBになってしまい、ワーカーが複数起動する設定だと、ワーカーの再起動を繰り返すだけになってしまいます。dotenvを入れていたら、この環境変数を追加しておきましょう。

MEMORY_AVAILABLE=1024