メドピア開発者ブログ

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

Rails APIサーバーで Ruby 3.2 の YJIT を有効化してみた。

サーバーサイドエンジニアの内藤(@naitoh) です。

Rails で構築された小規模な APIサーバー(Rails の API モードで構築したもの)で、Ruby 3.2 の YJITを有効化する事で性能アップすることができましたので、喜びを分かち合いたく共有させて頂きます。

shopify.engineering

We’re able to measure real speedups ranging from 5% to 10% (depending on time of day) on our total end-to-end request completion time measurements.

YJIT を開発した Shopify では 5%から10%の処理速度の改善があったという事で、以前から弊社でも本番で運用を開始したかったのですが、比較的検証のし易い APIサーバーで本番投入の準備が整ったので導入を実施してみました。

YJIT 有効化までの道のり

1. Ruby 3.2 へのアップグレード

YJIT は Ruby 3.1 では 実験的機能の位置付けでしたが、Ruby 3.2 以降では正式にサポートされました。

www.ruby-lang.org

  • YJIT は実験段階ではなくなりました
    • 1年以上にわたって本番環境でテストされ、安定して稼働する実績があります。

そのため、事前に Ruby 3.2 までアップグレードする必要があります。

2. YJIT 有効でBuildされた Ruby を用意する。

Ruby の DOCKER OFFICIAL IMAGE は YJIT 有効でBuildされているので、そちらを利用します。

$ docker pull ruby:3.2.2
$ docker run -it ruby:3.2.2 ruby --yjit -e 'p RubyVM::YJIT.enabled?'
true

--yjit オプションで YJIT有効で起動できる事が確認できました。

--yjit オプションを付けないで起動すると YJIT 無効で起動された事がわかります。

$ docker run -it ruby:3.2.2 ruby -e 'p RubyVM::YJIT.enabled?'
false

なお、YJIT無効でBuildされたRuby の場合は下記のようなエラーが出るので、この場合はYJITを利用できません。

$ ruby --yjit -e 'p RubyVM::YJIT.enabled?'
ruby: warning: Ruby was built without YJIT support. You may need to install rustc to build Ruby with YJIT.
-e:1:in `<main>': uninitialized constant RubyVM::YJIT (NameError)

p RubyVM::YJIT.enabled?
        ^^^^^^

※ 補足:Ruby 3.2 の YJIT は Rust-lang で実装されているため、Build 時には Rust-lang が必要ですが、実行時は Rust-lang は不要なため、Docker IMAGE には Rust のパッケージは含まれていませんでした。

3. YJIT 有効化

環境変数 RUBY_YJIT_ENABLE=1 でも YJIT有効化が可能なので、deploy 時の環境変数に設定する事で有効化しました。

YJIT 有効化後と有効化前(1週間前の同一曜日、グラフ破線部分)との比較になります。

Rack Request : p95 Latency 比較 (低い方が性能が良い)

Rack Request

ave: 35.6 ms -> 34.6 ms (2.8% 短縮) とLatency が改善された事がわかります。

Shopify の実績の 5%〜10%の改善に比べると半分程度の改善になりますが、今回はAPIサーバーでフロント周りの処理が無いので妥当かもしれません。

この Rack Request の結果が、Latencyの状況全体を表しているのですが、もう少し詳細を見ていきます。

ActiveRecord Instantiation : p95 Latency 比較 (低い方が性能が良い)

ActiveRecord Instantiation

Ave: 105.8 μs -> 77.9 μs (26.3% 短縮) と、こちらは改善度合いがかなり高いです。

Render Template : p95 Latency 比較 (低い方が性能が良い)

Render Template

Ave: 791 μs -> 701 μs (11.3% 短縮)と、こちらも改善度合いが高いですね。

Action Controller : p95 Latency 比較 (低い方が性能が良い)

Action Controller

Ave: 33.1 ms -> 32.7 ms (1.2% 短縮) と、こちらは効果は低めで、オーダー的にもここが改善されると効果が顕著に出ると思われます。

Request/CPU/Memory 状況

Rack Request 状況

Rack Request Hits

Max は 308 hits -> 282 hits と下がっていますが、Ave 116 hits -> 117 hits と、ほぼ同様なアクセス状況です。

CPU 使用率 (低い方が性能が良い)

Rails Container CPU

Ave 0.195(各コンテナの平均) -> 0.17(各コンテナの平均) とCPU使用率が低下しています。

Memory 使用量 (低い方が性能が良い)

Rails Container Memory

Ave 143(各コンテナの平均) -> 174.5(各コンテナの平均) と Memory 使用量が 22.0% 増加しています。 今回は Memory に余裕があったため大丈夫でしたが、事前にメモリの使用状況を確認してメモリに余裕がある状態でYJITを有効化した方が良さそうです。

まとめ

Memory 使用量が増える可能性はありますが、YJIT を ON にするだけで全体で 2〜3%のレイテンシ改善が見られました。 最新のRuby に追随することで Ruby 開発者の成果の恩恵を受けられるのは非常にありがたいです。

今年のRubyKaigi も直前ですが、どのような発表があるのか期待しております。


是非読者になってください


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

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

medpeer.co.jp

■エンジニア紹介ページはこちら

engineer.medpeer.co.jp