puma_worker_killer使ってみた

  • 15
    いいね
  • 0
    コメント

使用動機

開発しているRailsアプリケーションがnginx + pumaという構成なのですが、たびたびメモリの使用量が逼迫してアラート通知が来ているという状況があり、手動で再起動が行われていました。これが仮に休暇の時に起こったりすると大変です。
そこで、そのような状況をなんとかするためにpuma_worker_killerを導入しました。

puma_worker_killerで出来ること

https://github.com/schneems/puma_worker_killer

puma_worker_killerはpumaのworkerプロセスが規定のメモリ使用量を超えた時に自動でそのプロセスを殺して再起動します。
近い機能を提供してくれるものとして unicorn_worker_killer というものがあり、それのpuma版です。

設定と挙動

https://github.com/kzk/unicorn-worker-killer#usage に書いてありますが説明します(2016年10月10日現在のmasterブランチのものを元に進めていきます)。

puma.rb
before_fork do
  PumaWorkerKiller.config do |config|
    config.ram           = 1024 
    config.frequency     = 5    
    config.percent_usage = 0.65
    config.rolling_restart_frequency = 12 * 3600 
    config.reaper_status_logs = true
  end
  PumaWorkerKiller.start
end

この例ではプロセスがforkされる前にworker_killerが動作するようになっています。

config.ram はRAMの大きさをメガバイトで指定します。基本的にはサーバーのメモリの大きさ指定で問題ないと思います。
config.frequency はメモリの使用量を確認する頻度を秒で指定します。上の例だと5秒毎にメモリ使用量を監視しています。監視すると↓のようなログが流れます。

23:23:32 web.1   | [73437] PumaWorkerKiller: Consuming 273.2578125 mb with master and 2 workers.
23:23:37 web.1   | [73437] PumaWorkerKiller: Consuming 273.2578125 mb with master and 2 workers.
23:23:42 web.1   | [73437] PumaWorkerKiller: Consuming 273.27734375 mb with master and 2 workers.

config.percent_usageram で指定した値の何%を使用したらそのワーカーのプロセスを殺すかを指定します。今回の場合だと1024 * 0.65 = 665.6MB使用すると再起動がかかります。このようなログが出てワーカープロセスが再起動されます。

23:38:59 web.1   | [77845] PumaWorkerKiller: Out of memory. 2 workers consuming total: 665.66015625 mb out of max: 665.6 mb. Sending TERM to pid 77869 consuming 287.57421875 mb.
23:39:00 web.1   | [77845] - Worker 0 (pid: 78271) booted, phase: 0

ここで出ているtotalのメモリですが、puma本体のメモリ使用量と各workerのメモリ使用量の合計となっています(参考) 。
また、killされるプロセスは一番メモリ使用量の多いワーカープロセスとなります。

config.rolling_restart_frequency はrolling_restartの頻度を秒単位で指定しています。例の場合だと12時間に1回実行しています。
rolling_restart はメモリの使用量に関わらず、ワーカープロセスを順番にkillして再起動してくれる仕組みです。

最後に config.reaper_status_logs です。これがfalseだと、毎回のメモリ使用量を確認しにいく度に出力されていたログだけ出ないようになります。本番環境ではログが多くなってノイズになりがちなので設定しておくと便利だと思います。
ただし、これは現在の最新バージョンである0.0.6には入っていないオプションですので注意です。 0.0.7で正式に導入されました。

注意点

worker数が0だと動かない

worker_killerという名前がついてるので当然っちゃ当然なのですが、worker数が0だとworker_killerが動作しません。

prune_bundlerと一緒に使えない

prune_bundlerは、クラスターモード(複数ワーカー起動)時にbundlerを新しいものに切り替えてくれる機能で、これによりphased_restart(1つずつ順番にworkerを落として再起動してくれる仕組み。これにより再起動時のダウンタイムを無くせる)した時に最新のGemfileを見に行くようにする機能です。
phased_restartを行いたい時にほぼほぼ必須のオプションなのですが、これを設定するとpuma_worker_killerが使えなくなります。
というのも、prune_bundlerを使うとpuma.rb上でGemfileが読み込まれていない状態になり、puma_worker_killerに限らず他のgemをrequireしようとしてもファイルが見当たらずにエラーになります。
なので、puma_worker_killerを使う時はphased_restartを諦める必要があるのかなと思ってます。

おわりに

puma_worker_killerで出来ることや挙動の説明をしてきました。
サーバーでpumaを使っていてメモリの問題に悩んでる方がいましたら試してみてください。