LOG IN

jemalloc を導入しました

by やきたま

増え続けるスワップ使用量、鳴り止まないアラート、メモリ確保に失敗して死に絶える ElasticSearch。これは、とあるサーバーのメモリ使用量とのなんてことはない戦いの記録である――

この記事の目的

Mastodon を jemalloc オプションをつけてコンパイルした Ruby で動作させると幸せになれるよっていう日本語の布教記事です。

取り急ぎ、jemalloc を導入する

はやく jemalloc で幸せになりたい人は、ここだけ読んでください。導入方法はいくつかありますが、サーバーで動作している Ruby アプリケーションが Mastodon のみでしたので、jemalloc オプションを有効にした状態で Ruby を再コンパイルして、今まで使っていたそれと置き換える、という方法を取りました。

jemalloc API をサーバーにインストールする

Ubuntu ですと、jemalloc を使うための開発者用パッケージが準備されていますので、これをインストールします。

$ sudo apt install libjemalloc-dev

jemalloc を有効にして Ruby をコンパイルする

jemalloc パッケージをインストールしただけでは jemalloc は使用されません。コンパイルした実行ファイルは再コンパイルする必要があります。Ruby はコンパイル時にオプションをつけることで、jemalloc を有効にするよう指定できます。

$ su - mastodon

$ RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install 2.5.1

この処理にはかなりの時間がかかります。Production Guide にも「This will take some time. Go stretch for a bit and drink some water while the commands run.」って書いてますしね。

コンパイルが終わったら、念のためもう一度、使用する Ruby バージョンを再指定しておきます。

rbenv global 2.5.1

Mastodon サービスを再起動する

Ruby を使用するサービスだけでなく、Streaming サービスもしっかり再起動しておくほうがよいでしょう。Mastodon を実行する環境を変更していますからね。

sudo systemctl restart mastodon-*

心配な方は、mastodon-web、mastodon-sidekiq、mastodon-streaming をそれぞれ指定して restart するのもよいでしょう。

今後のメンテナンスにおける注意

今後、Mastodon が進化していく中で、もしかしたら利用する Ruby バージョンが変化するかもしれません。リリースノートをよく読み、Ruby の再コンパイルが必要であるというような手順が含まれている場合は、そのコマンドを実行するときに RUBY_CONFIGURE_OPTS=--with-jemalloc オプションを追加することを忘れないでください。

サーバーでは何が起こっていたのか

私の趣味は SSH でサーバーにつないで top を眺めたり journalctl -f で流れる謎の文字列を見たり Sidekiq のグラフならうえしたにうねってる様子を楽しんだりすることです。なので、わりと初期の頃から、私のインスタンスで Sidekiq サービスがメモリをたくさん占有し、長期間稼働したままだと次第にメモリ不足に陥るようだ、ということが分かっていました。その結果、同じサーバーに同居している ElasticSearch が(正確にはその VM が)、メモリ割り当てに失敗して異常終了する、という現象が発生していました。

もう少し詳しく調べる必要があった

Sidekiq が犯人であるかどうかを調べるためには、起動直後に Sidekiq がどれくらいメモリを占有していて、そして時間が経過するとそのメモリ占有量がどう変化するのかを、ちゃんと確認する必要がありました。例えば、起動直後のメモリ占有量ならこんな方法で(残念ながら、当時の再起動直後のメモリ使用量を調べることができませんでした。確か 400 MB をちょっと超えるあたりです):

$ sudo systemctl restart mastodon-sidekiq.service

$ systemctl status mastodon-sidekiq.service

そして、1 週間ほど経過してから、同様に調べてみると……

$ systemctl status mastodon-sidekiq.service

● mastodon-sidekiq.service - mastodon-sidekiq

Active: active (running) since 土 2018-07-28 14:52:59 JST; 6 days ago

Main PID: 18387 (bundle)

Tasks: 17

Memory: 1.2G

おかしいですね。ちょっとずつ増えていってるのか、それともあるタイミングで突然増えるのか、は、グラフにしたりして調べるべきだったんだと思いますが、さすがにそこまではしませんでした。

メモリリーク? それとも……

ちょっとずつソフトウェアのメモリ使用量が増えていく現象、身に覚えがありませんか。そうです、メモリリークです。ソフトウェアが必要なメモリを動的に確保して処理をおこない、その後不要になったらメモリを解放するという処理が必要になる場合があるのですが、これを忘れてしまっていたり、もしくは通常とは異なる処理(異常処理とか)をおこなった際に解放が実施されないとか、そういう不具合のことを言います。このメモリリークの問題は何年も昔からプログラマの課題になっていて、しかもツールとか使ってようやく見つけ出すことができるレアケースな不具合だったりして、現役のみなさまは頭を抱えたことがあるはず。

一方で、メモリリークについて心配しなくてもいい言語も存在します。ガベージコレクションです。これは、ソフトウェアを実行中に、ソフトウェアが確保しているメモリを走査して、すでに使われていないメモリ領域がある場合に自動的にそれを解放する機能です。ガベージコレクションにはいくつかの種類があって、どうやって「もう使われていない」と判断するのか、走査方法や解放する量、タイミングとかが細かく違い、それぞれに一長一短があります。

なぜこの話をしたかと言うと、Ruby にはガベージコレクションの機能がついているらしいのです。らしい、というのは、私は Ruby プログラマではありませんし、Ruby の仕様を深く理解していないからです。Ruby 初心者としては、「ガベージコレクションがあるのに、なぜ私のサーバーではまるでそれが働いていないのだろう?」というのは、当然の疑問でした。

jemalloc の登場

プロが教えてくれました。

jemallocを使ってみると、より積極的にメモリを解放してくれるかもしれません

引用元: https://mastodon.zunda.ninja/@zundan/100794971776870925

jemalloc は、malloc の実装のひとつです。malloc は動的にメモリを確保するための C 言語における標準ライブラリ関数です。メモリの割り当ても、なんかいろいろ手法というか戦略というか、そういうのがあるみたいですね。実装によって速度や効率が違ったりするみたいです。詳しいことはウェブの解説記事におまかせ。

導入後の様子

導入前は起動直後でも 400MB ほどの RAM を使用していたらしいのですが、Sidekiq に積み上がるジョブの様子にもよりますけど、jemalloc 導入後は 300MB 〜 400MB ほどで安定し、それ以上は増えません。ワーカー数を増やしてもさほどメモリ使用量は増えませんでした。

$ systemctl status mastodon-sidekiq.service

● mastodon-sidekiq.service - mastodon-sidekiq

Loaded: loaded (/etc/systemd/system/mastodon-sidekiq.service; enabled; vendor

Active: active (running) since 木 2018-10-18 12:24:43 JST; 3h 59min ago

Main PID: 19745 (bundle)

Tasks: 27

Memory: 349.7M

なんといっても、しばらく監視してると、ちゃんとメモリ使用量が減るところがすばらしいですね!

ところでその後

それまでメモリ使用量がカツカツで swap は常時空きなし、という状況だったのですが、jemalloc を導入したことでメモリに余裕ができたので、netdata をインストールしました。グラフ化できるといろいろはかどりますね。

OTHER SNAPS