メドピア開発者ブログ

集合知により医療を再発明しようと邁進しているヘルステックカンパニーのエンジニアブログです。読者に有用な情報発信ができるよう心がけたいので応援のほどよろしくお願いします。

Ruby × jemallocのすすめ

集合知プラットフォーム事業部・エンジニアの榎本です。コロナ禍の運動不足を解消すべく筋肉体操で筋トレを続けてますが、上腕三頭筋がいい感じに成長しており継続の大切さを身に沁みて実感しております。

目次

TL;DR(三行要約)

  • jemalloc でRubyアプリのメモリ効率改善
  • jemalloc でRubyアプリのパフォーマンス改善
  • jemalloc の導入も簡単

Rubyアプリケーションのメモリ肥大化問題

Ruby on RailsなどのRubyアプリケーションを運用する上で、メモリ使用量の肥大化に頭を悩ませた方は多くいらっしゃるのではないでしょうか。

下記は典型的なRailsアプリケーションのメモリ使用量のグラフです。メモリの使用量が対数関数のグラフのように時間とともに100%に近づいていく様子が見て取れます。

Railsアプリケーションのメモリの使用率の増加

この問題の素朴な対処法としては、しきい値を定義して定期的にworkerプロセスを再起動してやることです。実際にそれを実現するためのgemがいくつか存在します。

しかし puma_worker_killer の README 冒頭で注意喚起されているとおり、頻繁な再起動はCPUリソースを消費させパフォーマンス劣化の要因にもなることから、あくまで応急処置であるべきです。メモリ肥大化の根本的な原因となっているコードがあるのであれば、それをきちんと調査し修正・対応すべきでしょう。

jemalloc を使ってみる

こういったメモリの肥大化・断片化がなぜ起こるのか、それにどう対処すべきかについては書籍・『Complete Guide to Rails Performance』の中で詳しく解説されています1

追記: 本書の内容は下記の記事で紹介しております。

tech.medpeer.co.jp

細かい話は本を読んでいただくとして、本書の中ではRubyアプリケーションのメモリ使用を効率化する方法として、メモリアロケーターを jemalloc に切り替える方法が紹介されています。

前置きが少し長くなってしまいましたが、本記事では jemalloc を実際にプロダクション投入してみた結果とともに jemalloc について紹介したいと思います。

jemalloc とは?

jemalloc とは Meta(旧・Facebook)社が中心となって開発されているメモリアロケーターです2。その特徴として、メモリの断片化の回避とスケーラブルな並行性サポートを謳っています。

github.com

jemalloc で改善するのか?

気になるのは jemalloc 導入によって実際にメモリ使用率は改善するのか?というところです。

検索してみるとすぐに jemalloc 導入で実際にメモリの使用量が改善したという事例がいくつか見つかりました。

またこちらのベンチマークによると、メモリだけではなくパフォーマンスも10%程度 jemalloc によって向上することが示されています。

CRuby 2.5.0 jemalloc tcmalloc increase w/ tcmalloc increase w/ jemalloc
Median Throughput 175.13 req/sec 197.49 req/sec 183.33 req/sec 4.68% 12.77%

jemalloc の設定方法

Rubyはデフォルトのメモリアロケーターとして、 glibc malloc を使います。ではどのようにメモリアロケーターをデフォルトから jemalloc に切り替えることができるのでしょうか?

--with-jemalloc オプションをつけてRubyをコンパイルする方法もありますが、一番手軽な方法は 環境変数 LD_PRELOAD を設定することです。このLD_PRELOADに jemallocのsoファイルのパスを指定してやればOKです。

具体例を示しましょう。下記は alpineベースのRuby dockerイメージにおける jemalloc 設定方法になります(マルチステージビルドを使って jemalloc のインストールを行っていることに注意してください)。

FROM ruby:X.X.X-alpine as base
...
FROM base as jemalloc
RUN wget -O - https://github.com/jemalloc/jemalloc/releases/download/5.2.1/jemalloc-5.2.1.tar.bz2 | tar -xj && \
    cd jemalloc-5.2.1 && \
    ./configure && \
    make && \
    make install
...
COPY --from=jemalloc /usr/local/lib/libjemalloc.so.2 /usr/local/lib/
ENV LD_PRELOAD=/usr/local/lib/libjemalloc.so.2
...

jemalloc インストール後、ENV命令にてLD_PRELOADにsoファイルのパスを指定しています。この状態でRubyアプリケーションを起動すれば、Rubyのメモリアロケーターは jemalloc に切り替わります。

ただ、この環境変数はDockerイメージ全体のソフトウェアに影響するグローバルな設定なので、設定して問題ないかはきちんとステージング環境などで動作確認・検証しましょう。

jemalloc をプロダクション導入してみた結果

実際に本番環境で稼働するRailsアプリケーションに jemalloc を適用させてみました。その結果をご紹介します。

下記は sidekiq (v6.3)の jemalloc 導入前後一週間のメモリ使用率の比較です。青い線が導入後のメモリ使用率、点線が導入前のメモリ使用率となっております。

sidekiq jemalloc化 before(点線)/after(青線)

少しわかりにくいのですが、平均して5%程度メモリの使用率が改善したことが確認できました。しかし、先に紹介した改善事例のようにグラフにはっきりと改善が現れると期待していたので、正直なところ少し期待外れ感は否めませんでした。

パフォーマンスについてはどうでしょうか? 下記はRailsアプリケーション(Rails v6.1, Webサーバーはunicorn)のレイテンシーのグラフです。上から順に p99, p95, p90のレイテンシーとなっています。

Railsアプリケーションのjemalloc化 before/after

こちらはグラフにはっきりとした改善(10-20%程度のレイテンシーの改善)が現れました。Dockerfile を少しイジっただけでこれだけのパフォーマンスが改善したのは、期待以上の結果と言えます。

まとめ

Rubyのメモリアロケーターを jemalloc に切り替えることで、アプリケーションコードの変更なしにメモリ使用およびパフォーマンスを改善できました。

導入もさほど難しくないので、Rubyアプリケーションのメモリおよびパフォーマンスにお困りの方は一度試してみてはいかがでしょうか。

おまけ:jemalloc についてMatzに聞いてみた

弊社の技術アドバイザリーとしてMatzさんがおりますので、Matzさんにもjemalloc について見解を伺ってみました。

Q. jemalloc コアチーム的にどう考えている?

  • どのアロケーターでパフォーマンス向上するかは、アプリケーションの特性次第。一概にどれがいいとは言えないと思う
    • jemalloc に変える提案も来た3が、Rubyとして取り込む予定はない
    • LD_PRELOADでメモリアロケーターを変更できる口は用意してあるので、変更したい場合はそちらを使ってほしい
  • Ruby として jemalloc を推奨することはしないが、Railsアプリケーションでメモリがボトルネックになりやすいということは理解しているので、改善は進めている。すでに入れた変更だと下記のようなもの。
    • 世代別GC
    • インクリメンタルGC
    • Object Compaction
  • Rubyとして推奨はしないがコミュニティの中で jemalloc のほうが良さそうだといった知見が公開されるのは大歓迎である

メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html