こんにちは。サーバーサイドエンジニアの @atolix_です。
今回はメドピアで本番運用をしているアプリケーションの1つであるやくばと for Clinicにて、Ruby 3.2からRuby 3.3にアップデートを行った際のパフォーマンスの変化を計測しました。
Ruby 3.3ではYJITの大幅な改善が含まれているので、これによるアプリケーションへの影響を確認していきます。
前提
本記事に記載されたデータは以下の条件で計測をしています。
- Rails: 7.1.3.4
- YJIT有効化時のオプションは特に付与していない状態(Dockerfileから環境変数を与えて有効化)
- 3.2.4(+YJIT)から3.3.1(+YJIT)へのアップデート
- 有効化前後の1週間を比較
パフォーマンスの変化
やくばと for Clinicではモノレポのアプリケーション内で、クリニックAPI・患者APIといった括りでエンドポイントおよびサーバーを分割しているので、それぞれのAPI Latency/CPU/Memoryの変化を確認していきます。
API Latency(低い方が性能が良い)
クリニックAPI

| パーセンタイル | before(avg) | after(avg) |
|---|---|---|
| p50 | 49ms | 45ms |
| p90 | 344ms | 310ms |
| p95 | 518ms | 470ms |
| p99 | 644ms | 580ms |
約9~10%の短縮がされているので、かなり改善されていることが分かります。
患者API

| パーセンタイル | before(avg) | after(avg) |
|---|---|---|
| p50 | 132ms | 104ms |
| p90 | 742ms | 523ms |
| p95 | 888ms | 619ms |
| p99 | 937ms | 645ms |
患者API側はより大きく効果が出ており、全体で30%弱の短縮が実現できています。
CPU使用率(低い方が性能が良い)
クリニックAPI

| before(avg) | after(avg) |
|---|---|
| 0.42% | 0.34% |
0.08%減で改善されているようです。
患者API

| before(avg) | after(avg) |
|---|---|
| 0.73% | 0.51% |
0.22%減でクリニックAPIよりも大きく効果が出ていそうです。
メモリ使用率(低い方が性能が良い)
クリニックAPI

| before(avg) | after(avg) |
|---|---|
| 441MiB | 455MiB |
メモリ使用率は3%ほど増加しています。
患者API

| before(avg) | after(avg) |
|---|---|
| 517MiB | 533MiB |
こちらもメモリ使用率は3%ほど増加しています。
RubyVM::YJIT.enableでの有効化
Ruby 3.3からのYJITの変更点として、RubyVM::YJIT.enableの実装も挙げられます。
これまでのRubyでは環境変数RUBY_YJIT_ENABLE=1の設定やコマンドラインで--yjitの付与をしないとYJITを有効化することができませんでしたが、Ruby 3.3からはコード内でRubyVM::YJIT.enableを呼び出すことでYJITの有効化が行えるようになりました。
開発中のRails7.2ではデフォルトでRubyVM::YJIT.enableを呼び出すinitializerが実装される予定です。
また、YJITの起動方法をRubyVM::YJIT.enableに切り替えることでメモリ消費量の点でもメリットがあります。
もう一つの利点は、YJITの起動を遅延させることで、 アプリ初期化後は使われないコードのコンパイルを避けメモリ消費量を削減できる点である。 Railsのイニシャライザでも効果はあるが、理想的にはUnicornのafter_forkやPumaのafter_worker_forkから呼び出すと良い。 これにより、起動するワーカーの半分だけYJITを有効化し、インタプリタと性能を比較する基盤として利用することもできる。
という訳でYJITの有効化方法をDockerfile上での環境変数からRubyVM::YJIT.enableに切り替えて、上記のメモリ消費量から更にどのような変化が現れるか1週間分の計測をします。
Rails7.2の実装と同じようにinitializer配下にRubyVM::YJIT.enableを呼び出すコードを配置します。
if defined? RubyVM::YJIT.enable Rails.application.config.after_initialize do RubyVM::YJIT.enable end end
クリニックAPI

| Ruby 3.2.4(avg) | Ruby 3.3.1 環境変数によるYJIT有効化時(avg) | Ruby 3.3.1 RubyVM::YJIT.enableによるYJIT有効化時(avg) |
|---|---|---|
| 441MiB | 455MiB | 424MiB |
455Mib -> 424MiBなので7%ほどの減少で効果が出ていそうです。
患者API

| Ruby 3.2.4(avg) | Ruby 3.3.1 環境変数によるYJIT有効化時(avg) | Ruby 3.3.1 RubyVM::YJIT.enableによるYJIT有効化時(avg) |
|---|---|---|
| 517MiB | 533MiB | 503MiB |
533MiB -> 503MiBで6%ほど消費量が抑えられています。
まとめ
既にYJITを有効化しているRuby 3.2からのアップデートでも大幅なAPIレイテンシ/CPU使用率改善が確認できました。
また、RubyVM::YJIT.enableによってメモリ消費量の削減も確認できたので、まだ有効化方法を切り替えていないプロジェクトでは積極的に導入するのが良さそうです。
一方でプロジェクトによってはメモリの使用量が増加する場合も有り得るのでアップデート前後で継続的な監視が必要そうです。
是非読者になってください!
メドピアでは一緒に働く仲間を募集しています。
ご応募をお待ちしております!
■募集ポジションはこちら medpeer.co.jp
■エンジニア紹介ページはこちら engineer.medpeer.co.jp
■メドピア公式YouTube www.youtube.com
■メドピア公式note
style.medpeer.co.jp