Open9

RailsのAPサーバーのメモリ使用率が常に上昇していく現象に対処する

yuto_byteyuto_byte

Render.comにRuby on Railsを乗せたAPサーバーを作成して運用しているのだが、メモリ使用率がすごい速度で上昇していって、半日ごとにサーバーがダウンしてしまっているので対処法を調べていく。

yuto_byteyuto_byte

▶︎現在の状態

リクエスト数

リクエスト自体は減少傾向にあるにも関わらず、基本的にはずっと同じようにメモリが右肩上がりになってはのサイクルを繰り返している

使用率が100%に達してメモリ不足が発生する頻度はリクエスト数が多かった初日の方が頻度が高いが、メモリが右肩上がりになってることには変わりない。

yuto_byteyuto_byte

原因(?)

最初はメモリリークを疑ったけど、どうやら「メモリの断片化」というRubyのメモリ管理の仕様が関わっているっぽい。

確かに先ほど添付した画像を見れば分かる通り、自分のメモリの肥大化の曲線は対数的曲線を描いている。 
メモリリークの場合は直線上での上昇の傾向があるらしい。

参考記事:
https://techracho.bpsinc.jp/hachi8833/2022_06_23/50109
https://tech.studyplus.co.jp/entry/2019/09/09/094140
https://www.petitmonte.com/ruby/jemalloc.html

まず「メモリ」というものを知るには =>
https://zenn.dev/tomoikey/articles/ab2b065bdf334c

yuto_byteyuto_byte

「メモリの断片化」とは

断片化は「フラグメンテーション」と呼ばれることが多い。

フラグメンテーションは、『メモリの空き領域はあるのだが、小さな領域が飛び飛びに存在する、虫食いのような状態になってしまい、実際には使用できない状態になってしまったこと』を指すそう。

これが積み重なると大きな領域がどんどん無くなっていくため、最終的に確保できるメモリが無くなっていって逼迫してしまうそうだ。

これを踏まえると、時間とともに確保できるメモリが少なくなっていくことから、「メモリの断片化の場合は、メモリの使用率の推移は対数的な曲線になる。」というのがよく分かる。

分かりやすい記事:
https://uquest.tktk.co.jp/embedded/learning/lecture17.html

yuto_byteyuto_byte

解決するための手法

①最大アリーナ数を減らす

MALLOC_ARENA_MAX環境変数でメモリアリーナの最大数を変更する。
全体のパフォーマンスとトレードオフになってしまうらしく、推奨されていない。

②puma_worker_killer

puma全体のメモリ使用量が設定した値を超えると、workerを再起動することでメモリの肥大化を防いでくれる。

しかし、githubのREADMEにも書かれている通り、事後的な応急処置的手段であり非推奨となっている。
https://github.com/zombocom/puma_worker_killer

③jemalloc👈今回はこれ

jemalloc は、 Meta社が中心となって開発しているメモリアロケーター。**「メモリの断片化を抑える」**という特徴を持っている。

Rubyとしては推奨していないもののメモリ断片化によるメモリ肥大化を抑える上で比較的有効とされている。 他の手段と比べて本質的な部分に対する対処に近く、これを採用されるケースが多い。

yuto_byteyuto_byte

本番環境にjemallocを適用するためにやったこと

現状、Render.comにデプロイしている環境はrender-build.shを使用しているため、jemallocが適用されていない。

よって、本番環境用のDockerfileを作成して、Dockerfileの中身でapt-get install libjemalloc2を書いて、docker-entrypointで以下のjemallocを有効化するコードを書いた。

if [ -z "${LD_PRELOAD+x}" ] && [ -f /usr/lib/*/libjemalloc.so.2 ]; then
  export LD_PRELOAD="$(echo /usr/lib/*/libjemalloc.so.2)"
fi

これをRender.comにデプロイしてあげる。

注意点:

  • 新しいサーバーを立てるので本番環境の環境変数をちゃんと元のサーバーを見て用意する
  • 今回はVercelにReactを置いていてRails側はAPIモードで連携していたので、Vercel側の環境変数を新しいサーバーのものに変更する
  • 無料枠でGASでsleep対策してる場合、叩き起こす先のURLを変更する
yuto_byteyuto_byte

Render.comのShellから確認

Render.comのmanageにあるShellから以下の行を打つと、jemallocが適用されているか確認ができる

ldconfig -p | grep jemalloc

結果=>

root@srv-xxxxxxxx:/app# ldconfig -p | grep jemalloc
        libjemalloc.so.2 (libc6,x86-64) => /lib/x86_64-linux-gnu/libjemalloc.so.2

ちゃんと返ってきている! あとはメモリ使用率の推移のメトリクスが貯まり次第、効果を比較してみようと思う。

yuto_byteyuto_byte

効果

25%ぐらい改善されてるっぽい!

元々すぐに98%付近になってたけど、Dockerでデプロイするようにしてjemallocに変更したら75%付近を推移するようになりました!(色んな記事を見る限り5%〜35%の範囲での改善が一番多かった)

※リクエスト数が減ってるからそれが影響しているだけという可能性もゼロではない。

yuto_byteyuto_byte

結論

結論としては、render-build.shは便利だけどjemallocなど色々最適化がしにくい面があるので、本サービスとして継続したいなら番環境用のDockerfileやらdocker-entrypointをデプロイする方が良いなという結論です。