物件基盤システムをRuby 3.3にアップデートし、YJITを有効にした結果

こんにちは!
イタンジ株式会社でバックエンドエンジニアをしている藤崎 (https://x.com/aki19035vc) です。
イタンジの各種サービスの要である物件基盤システムを開発をしています。

私がメインで見ているRailsアプリケーションのRubyバージョンを3.3系にアップデートしましたので、その結果についてご紹介いたします。

前提

今回アップデートしたRailsアプリケーションの特徴は下記の通りです。

  • Ruby 3.2.1
  • Rails 7.0.6
  • YJITは有効になっていない
  • APIモードで動作しており、レスポンスはJSONしか返さない
  • テストのラインカバレッジは100%
  • 型の記載率は(ほぼ)100%

記載の通りテストのラインカバレッジを100%に維持しています。

また、型に関しても「appディレクトリ以下」は100%書かれています。 ハッシュの中身などは untyped していますが、メソッドの引数や戻り値そのものを untypedにしている箇所はありません。 メタプロしている場合はgeneratorを作ったり自前で頑張って書いたりしています。

そのため、既存のテストや型検査が通ればアプリケーションに重大な破綻が起きていない事に自信を持つ事ができます。

なお、デプロイ先はAWS ECS Fargate で、Railsアプリケーションが起動しているタスク定義は下記の通りです。

  • タスクサイズ
    • CPU: 1024 (1 vCPU)
    • メモリ: 2048 (2 GB)
  • ランタイムプラットフォーム
    • オペレーティングシステムファミリ: LINUX
    • CPUアーキテクチャ: ARM64

事前準備: Ruby 3.2でYJITを有効化

Ruby 3.3 の力を最大限引き出すために、まずRuby 3.2の状態でYJITを有効にするところから始めました。

なお、YJITについては調べれば多くの記事が出てくると思いますので、ここでは解説しません。

Ruby 3.2でYJITを有効にするにはいくつか方法がありますが、

  1. 環境変数 RUBY_OPT--yjitを設定する
  2. 環境変数 RUBY_YJIT_ENABLE=1を設定する

今回は一番上のRUBY_OPTを使用する方法でYJITを有効にすることにしました。

YJITを有効にするとメモリ使用量が増加することが予想されますが、RUBY_OPT--yjit-exec-mem-size=32 のように設定することでYJITを有効にしつつメモリ使用量も制御することができるためです。

結果

YJITを有効化して様子見をしていましたが、特に問題が発生することはありませんでした。

下記のグラフは、YJITを有効にした前後1週間の6時間ごとのレスポンスタイムです。 0.05秒程度は速くなっていそうです。

※ 1時間ごとで計測すると変化が見づらいので6時間ごとにしました。

想定の範囲内でしたが、メモリ使用率も増加していました。 こちらは1時間ごとのものになります。

本題: Ruby 3.3へアップデート

ようやくRuby 3.3のアップデートの話に入れます。

といっても、DockerfileやGemfileを変えたりするくらいです。RUBY_OPTで指定している値も変更する必要はありません。

Ruby 3.3 からはRubyVM::YJIT.enableを使用してアプリケーションの初期化時に動的にYJITを有効にすることもできますが、アップデートが無事終わった後に以下のPRを参考にイニシャライザで有効化しようと考えています。

github.com

結果

Ruby 3.3にアップデートして2週間ほど様子見をしていましたが、特に問題が発生することはありませんでした。

下記はアップデート前後1週間の、1時間ごとのレスポンスタイムです。
速くなっていますね!素晴らしいです!!

更にECSタスクの起動台数も減りました!
CPU使用率が一定の割合になるようにオートスケーリングが設定されているのですが、1日の平均起動台数が下記のように変化しました。

※ 1時間ごとにすると分かりづらいため、1日の平均起動台数にしています。

まとめ

本記事では、YJITが有効になっていないRuby 3.2のRailsアプリケーションをRuby 3.3にアップデートした結果についてご紹介いたしました。

Ruby 3.3にアップデートしYJITを有効にするだけで、パフォーマンスが改善するだけなく、ECSタスクの起動台数が減ってコストまで下げることができました。

※ 今回の結果はあくまで弊社の事例であり、あらゆるケースで必ずしもパフォーマンスが改善するとは限りません。

最後に

Rubyのアップデート後にRails 7.1へのアップデートも行ったのですが、その際にいくつかハマりポイントがありました。

その時の話は別の記事として書いていますので、ぜひご一読いただけますと幸いです。

tech.itandi.co.jp