inSmartBank

B/43を運営する株式会社スマートバンクのメンバーによるブログです

Ruby 3.3.2 (+YJIT) アップデートによるパフォーマンス改善レポート

はじめに

こんにちは!株式会社スマートバンクでサーバサイドエンジニアをしている @nagasawa です。
2024年6月より弊社では Ruby 3.3.2 を本番アプリケーションで稼働させ始めたため、バージョンアップ前と比較してどの程度パフォーマンスに変化が現れたのかをご紹介いたします。 また、今回を機に YJIT Metrics の可視化と YJIT の遅延起動にも取り組んだため、その手法や効果についてもこの記事内でシェアできればと考えています。

前提

下図のシステムアーキテクチャ図の通り、弊社では Ruby と Rails で開発されたいくつかのシステムを稼働させています。 この記事では core-api と呼ばれている私達が開発してる「家計簿プリカ B/43」の機能のほぼ全てを提供しているシステムのパフォーマンス変化をご報告いたします。

バージョンアップ前 バージョンアップ後
Ruby 3.2.2 + YJIT 3.3.2 + YJIT
Ruby on Rails 7.1.3.4 (アップデートなし)

パフォーマンス改善

Ruby 3.3.2 のデプロイ(2024/6/6 15:00 頃)後を起点に前後一週間ほどの期間で比較した結果です。結論として、レスポンスタイム、CPU使用率、メモリ使用率はどれも大幅に数値が改善していました!!

  • レスポンスタイム

    • p99 : -9.65%
    • p90 : -8.68%
    • p50 : -4.33%
    • average : -6.89%
  • CPU使用率:-7.77 %

  • メモリ使用率:-3.84%

YJIT の遅延起動

また、Ruby 3.3.2 へのバージョンアップの1週間後に YJIT の遅延起動も行いました。 タイミングは Rails.application.config.after_initializeRubyVM::YJIT.enable を実行する形で実現しています。 これによりアプリケーションの初期化時にしか使われていない YJIT コードの生成が行われず、メモリ消費量を削減することが期待できます。

参考 k0kubun.hatenablog.com

YJIT Metrics の取得

とにもかくにも数値を見るためには可視化をしなければいけません。
弊社では YJIT Metrics を確認できる状態ではなかったため、まずはメトリクスを取得できるようにするところを @osyoyu さんが取り組まれました。
弊社が APM ツールとして使用している New Relic の Agent には、YJIT Metrics をよしなに記録してくれる機能は存在しなかったため、独自の Rack Middleware を挟みリクエストの1%で起動させ特定のYJIT Metrics を送信するようにしています。メトリクスの保存には、New Relic のカスタムメトリクスを使用しています。
また、今回いくつか確認したいメトリクスのうち、ratio_in_yjit に関しては Ruby の起動時にオプションとして --yjit-stats を渡す必要があるため、 環境変数(RUBYOPT)経由で渡すよう変更を加えています。

サンプルコード

module Rack
  class YJITStatsExporter
    TARGET_METRICS = %i[
          metrics_name
    ]
    SAMPLING_RATE_PERCENTAGE = 1

    def initialize(app)
      @app = app
    end

    def call(env)
      @app.call(env)
    ensure
      if Settings.yjit_stats_reporting.to_i == 1
        if RubyVM::YJIT.enabled? && Random.rand(100) < SAMPLING_RATE_PERCENTAGE
          stats = RubyVM::YJIT.runtime_stats
          stats.each do |key, value|
            next unless TARGET_METRICS.include?(key)

            NewRelic::Agent.record_metric("Custom/YJIT/#{key}", value)
          end
        end
      end
    end
  end
end

Rack Middleware でメトリクスを送信する手法は、こちらの記事を参考にしています。

railsatscale.com

メモリ使用量の改善

こちらも想定より効果があり、メモリ使用量が大幅に削減されることがわかりました! 下図のグラフは、デプロイ後とその前週の数値を描画したもので

  • 実線は、デプロイした週の数値
  • 点線は、デプロイした週の前週数値

となっています。 縦線箇所以降が遅延起動のデプロイをしたタイミングなのですが、デプロイ以降は YJIT により生成されたコードの記録で使われているメモリ量が減っていることがわかります。 yjit_alloc_size と合わせ、15MB ほどの削減をすることができていました!

各メトリクスの意味は、下記の記事を併せてご参照ください! gihyo.jp

  • code_region_size

  • yjit_alloc_size

  • ratio_in_yjit
    デプロイ前と変わらず ratio_in_yjit は99%台をキープしています。

まとめ

今回、弊社アプリケーションでも Ruby 3.3.2 へアップデートすることによって

  • レスポンスタイム
  • CPU使用率
  • メモリ使用率

を大幅に改善することができました!🎉
また、YJIT の起動を遅延させることでメモリ消費量も削減することもでき嬉しいこと尽くしでした。 パフォーマンスが大幅に改善された Ruby とそのコミッターの方々には大変感謝しています。 本記事がこれから Ruby 3.3.2 + YJIT へアップデートされる方のお役に立てば幸いです!

スマートバンクでは、サーバーサイドエンジニアを募集しています!

smartbank.co.jp

また、弊社の開発について話を聞いてみたい方はぜひカジュアル面談もお待ちしています!

smartbank.co.jp

We create the new normal of easy budgeting, easy banking, and easy living.
In this blog, engineers, product managers, designers, business development, legal, CS, and other members will share their insights.