- Ruby / Rails関連
READ MORE
原著者の許諾を得て翻訳・公開いたします。
Fullstaq Rubyは、標準のMRI Rubyインタプリターのカスタムビルドです。メモリアロケーターの置き換え、セキュリティパッチの適用などさまざまな手を加えてあります。
Rubyを長くやっている人なら、なつかしのREE(Ruby Enterprise Edition)を思い出すことでしょう。REEはRuby 1.8.7やRuby on Rails 2.2といういにしえの代物です(ほぼ10年前!)。あの時代が懐かしくなります。REEは今でもRVMやrbenvでインストールできますし、レガシーアプリケーションの中には今も引き続き実行できるものもあれば、とっくに移行したものもあります。REEはパフォーマンス向上、メモリ削減、古いセキュリティ設定の調整などのため、Ruby 1.8.7にさまざまなパッチを適用していました。
それらの問題はMRI 1.9.xでほとんど解決し、MRI 1.9.xの利用が広まるにつれてREEは時代遅れになってきました。しかし現代の「vanilla(訳注: ピュアな)」MRIですら、比較的簡単に修正できるほころびがいくつもあります。中でも腹立たしいのはメモリ断片化によるメモリ肥大化です。
そんなわけで、REEの作者であるHongli LaiがFullstaq Rubyをリリースしたことはまったく不思議ではありません。
REEは死んだ、Fullstaq Ruby万歳!
Evil Martiansが手掛けるプロジェクトのひとつで、深刻なメモリ肥大化が発生したことがありました。アプリケーションには膨大なIOがあり、高コンカレンシー設定を施したSidekiqプロセスも山のようにありました(1プロセスあたり20スレッド)。この設定はパフォーマンスの観点から最適です。理由は、ワーカーがさまざまなリモートAPIや自社内データベースやキャッシュにリクエストを送信することがほとんどだからです。しかしコンカレンシーをこれほど高く設定すると、メモリ断片化も大きく進みます。私たちのSidekiqプロセスのひとつひとつが「数GB単位で」メモリを食らうのです。
Sidekiqのコンカレンシー設定について詳しくはNate Berkopecの「Sidekiq in Practice part 1」記事をご覧ください。
私たちはMRI 2.6.3から、Fullstaq Ruby 2.6.3+jemallocに移行することを決定し、どんなふうに動くかをチェックすることにしました。
私たちは、production環境で動作する商用アプリケーションでFullstaq Rubyを試してみました。このアプリケーションは、支払いクライアントからのリクエストを24時間ぶっ通しでさばきます。
結論: どこもぶっ壊れず、ダウンタイムもゼロでした!
今度は監視グラフを見てみましょう。長時間実行しているプロセスのメモリ肥大化は、事実上消え去りました!
レスポンスタイムやCPU利用率には何の違いも見いだせませんでした。
上のグラフから、メモリ消費がかさんだ原因はメモリ断片化であったことが証明されました。
Rubyのバイナリを差し替えるだけで相当の改善を得られたのです。
jemallocをオプションとして選択できない、またはMRIを他のものに差し替えるわけにいかないのであれば、Ruby標準のglibc mallocの振る舞いを調整するMALLOC_ARENA_MAX=2
という呪文をお試しください。結果はFullstaq Rubyとほぼ同じと言ってよいレベルです。
MALLOC_ARENA_MAX=2
のベンチマークについて詳しくは弊社のブログ「Cables vs. malloc_trim, or yet another Ruby memory usage benchmark」をご覧ください。
かくして私たちはFullstaq Rubyを使い続けることに決めました。
記事執筆時点では、Fullstaq Rubyをインストールする方法はdebパッケージまたはrpmパッケージしかありません(直接またはリポジトリ経由でのインストール)。しかし私たちのアプリはKubernetesクラスタにデプロイするので、Dockerイメージが必要になりました。Fullstaq Rubyには公式の「コンテナエディション」がないので、独自のイメージをビルドしてみましょう。大した手間ではありません!
訳注: 翻訳記事公開時点では、Fullstaq Rubyサイトのコンテナエディションは”coming soon”となっています。
fullstaqruby.orgより
公式のRuby Dockerイメージで使われているLinuxディストリビューションに合わせてDebian 9を使うことにし、Rubyのバージョンを定義します。
FROM debian:stretch-slim
ARG RUBY_VERSION=2.6.3-jemalloc
続いて必要なソフトウェアをインストールし、Fullstaq Ruby APTリポジトリを追加し、Rubyそのものをインストールしてaptキャッシュをクリーンアップします。以上を1つのコマンドに集約することで、Dockerのレイヤのサイズを削減します。
RUN apt-get update -q\
&& apt-get dist-upgrade --assume-yes\
&& apt-get install --assume-yes -q --no-install-recommends curl gnupg apt-transport-https ca-certificates\
&& curl -SLf https://raw.githubusercontent.com/fullstaq-labs/fullstaq-ruby-server-edition/master/fullstaq-ruby.asc | apt-key add -\
&& echo "deb https://apt.fullstaqruby.org debian-9 main" > /etc/apt/sources.list.d/fullstaq-ruby.list\
&& apt-get update -q\
&& apt-get install --assume-yes -q --no-install-recommends fullstaq-ruby-${RUBY_VERSION}\
&& apt-get autoremove --assume-yes\
&& rm -fr /var/cache/apt
Fullstaq Rubyではrbenvも依存関係としてインストールしますが、Dockerでは不要なので、以下のようにRubyの公式Dockerイメージと同じ方法でシステムの$PATH
にRubyとgemのバイナリを追加しましょう。
ENV GEM_HOME /usr/local/bundle
ENV BUNDLE_PATH="$GEM_HOME"\
BUNDLE_SILENCE_ROOT_WARNING=1\
BUNDLE_APP_CONFIG="$GEM_HOME"\
RUBY_VERSION=$RUBY_VERSION\
LANG=C.UTF-8 LC_ALL=C.UTF-8
# おすすめのパス: https://github.com/bundler/bundler/pull/6469#issuecomment-383235438
ENV PATH $GEM_HOME/bin:$BUNDLE_PATH/gems/bin:/usr/lib/fullstaq-ruby/versions/${RUBY_VERSION}/bin:$PATH
CMD [ "irb" ]
以上でできあがりです!
このイメージは既にビルドして公開してあります。quay.ioにある私たちのリポジトリからpullしていただいて構いません。
docker pull quay.io/evl.ms/fullstaq-ruby:2.6.3-jemalloc-stretch-slim
Dockerfileはgithub.com/evilmartians/fullstaq-ruby-dockerにあります。
私たちのアプリケーションのDockerfileでベースイメージを以下のように置き換えられます。
-ARG RUBY_VERSION=2.6.3
+ARG RUBY_VERSION=2.6.3-jemalloc
-FROM ruby:${RUBY_VERSION}-stretch-slim
+FROM quay.io/evl.ms/fullstaq-ruby:${RUBY_VERSION}-stretch-slim
あとはstagingにデプロイし、最後にproductionにデプロイします。
皆さんもどうぞご自由にお使いください!