RailsアプリケーションでPumaを使ったり、非同期処理のためにSidekiqを使うことはよくあると思います。
アプリケーションを長期運用しているとPumaやSidekiqのメモリ使用量が肥大しOOM Killerにプロセスを殺されたり。なんてことがよく起きます。
原因
メモリ使用量が肥大化する原因の一つとして、Rubyのメモリ管理とmallocの仕様(スレッド単位メモリアリーナ)によりメモリの断片化が発生していることが挙げられます。
メモリの断片化であって、メモリリークしているわけではありません。
解決策1. Puma Worker Killerを使う
Puma Worker KillerというGemがあります。
PumaのWorkerプロセスが設定したメモリを超えた時に自動でそのプロセスを殺して安全に再起動することでメモリの肥大を防ぎます。
根本的な解決ではありませんが、環境をいじれない人にとってや有効かもしれません。
ただPumaのみの解決であってSidekiqの解決にはなりません。
解決策2. メモリアリーナの最大数を変更する
MALLOC_ARENA_MAX環境変数でメモリアリーナの最大数を設定することによってメモリの断片化が改善できます。
MALLOC_ARENA_MAX=2が最適値と言われています。
ちなみにHerokuではMALLOC_ARENA_MAX=2がデフォルトになっているようです。
ただこちらもトレードオフとしてレスポンスタイムが悪化するという実験結果もあります。
https://devcenter.heroku.com/articles/testing-cedar-14-memory-use
解決策3. jemallocを使う
個人的にはこちらの方法をおすすめします。
アロケーターはmallocからjemallocに変更することで解決できる可能性があります。
jemallocもスレッド単位アリーナを実装していますが、mallocで起きる断片化を回避する設計となっています。
Rubyの再ビルドが必要になるので既存のアプリケーションに導入する場合はしっかり検証してください。
手順
jemallocのインストール
sudo yum install jemalloc-devel
rbenvでrubyをビルド
RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install X.X.X
導入されているか確認
irbで確認します
$ irb
irb(main):002:0> RbConfig::CONFIG['MAINLIBS']
> "-lz -lpthread -lrt -lrt -ljemalloc -lgmp -ldl -lcrypt -lm"
ljemallocが入っていればOK
参考