昨年、こんな記事を書いていました。
これで設定できていたと思ったのですが、アクセスが集中したときにワーカーの再起動が起きず、スワップが発生してR14(メモリ関連のエラー)が頻発していました。このときに行った設定はどうも一定時間経過したらpumaのワーカーを再起動するという設定だけで、メモリ使用量が増えたらワーカーを再起動させる設定をしていなかったようでした(完全にミス…)。
puma_worker_killerの設定を行う
puma_worker_killerの設定はREADMEに書いてあったので、今回はそれをちゃんと行いました。
まず、 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