次のグラフは、Herokuで運用している、とあるRailsアプリケーションのMemory Usageです。1日1回再起動がかかったあと、スワップが発生するまでメモリ使用量が増え続けます。特に大きなデータをメモリ上に置き続けているわけではありません。
Passengerの作者Hongli Lai氏が、Rubyのメモリが膨れ上がる問題について研究して記事を書いています(2019年3月)。
日本語での概略は、次の記事の真ん中あたりで読めます。
Hongli Lai氏が見つけたのは、Rubyはメモリを正しく解放しているが、Cのライブラリ(glibc)のmalloc周りがなかなかメモリを回収してくれない、ということです。メモリ使用量よりパフォーマンスを優先しているせい、との見立てです。
メモリ使用量が膨れ上がるのを防ぐ方法は、3つあります。1と2は以前から知られた方法で、3はHongli Lai氏が発見した方法です。Hongli Lai氏の説が正しければ、3が本質的な解決方法ということになります。
- mallocの代わりにjemallocを使ってRubyをコンパイルする。
- 環境変数 MALLOC_ARENA_MAX=2 を指定する。
- ガベージコレクションの後でmalloc_trimを呼ぶ。
Hongli Lai氏は、この件について一緒に研究してくれるよう呼びかけています。
RubyのBug trackerでも報告されていますが、話は進んでいないもよう。
Rubyに上記の3のパッチを当てるプロジェクトが公開されています。残念ながらHeroku用は開発が進んでいないようです。
Herokuでは、上記の2の環境変数MALLOC_ARENA_MAX=2はデフォルトになりました。ただし、2019年9月24日より前に作られたアプリケーションでは自分で設定する必要があります。
MALLOC_ARENA_MAXを指定すると、レスポンスタイムが若干増えるという実験結果があります。
以上です。私自身は上記1-3のどれも実際には試していません、すいません。🙇♂️