Ruby

Rails 7 & Ruby 3.2 パフォーマンスと YJIT ガイド

ⓘ本ページはプロモーションが含まれています

スポンサードリンク

1. Rails 7 と Ruby 3.2 がもたらす主なパフォーマンス改善

項目 Rails 7 の変更点 (概要) Ruby 3.2 の変更点 (概要)
コードロード Zeitwerk がマルチスレッド対応し、ローダーが同時に複数のファイルを読み込めるようになった。
Zeitwerk::Loader.eager_load の実行時間が約 15 % 短縮 (公式ベンチマーク)
YJIT が デフォルトで有効化されていない が、フラグ --yjit または環境変数 RUBY_YJIT_ENABLE=1 によりオンにできる。ホットメソッドの即時 JIT コンパイルが可能になる。
サーバー設定 Puma のデフォルトスレッドプールが 5:5 → 5:16 に拡張。リクエスト待ちキューが減少し、同時接続数が約 30 % 増加。 GC(Garbage Collector)の世代分離とインクリメンタルマーキングを導入。GC ポーズ時間の 平均 25‑35 % 短縮 が報告されている (Ruby 3.2 リリースノート)。
キーワード引数 ruby -w で警告が出なくなり、内部的にハッシュ生成を回避する最適化が追加。 キーワード引数の処理ロジックが C レベルで高速化され、典型的な API 呼び出しで 約 20 % のレイテンシ削減 が期待できる (ベンチマークは benchmark/ips 参照)。
フロントエンド Hotwire(Turbo + Stimulus)が標準装備。ページ遷移回数が減り、サーバー側の HTML 生成回数が削減される。 該当なし (Ruby 側の変更はなく、Rails の機能として提供)。

注記
YJIT は Linux x86_64 環境でも「デフォルトで有効」ではありません。ruby -v だけでは --yjit が自動付与されず、明示的にフラグまたは環境変数を設定する必要があります(公式ドキュメント参照)。


2. YJIT の仕組みと有効化手順

2.1 YJIT とは

  • 名称: Yet another JIT
  • 実装位置: MRI インタプリタの内部に統合されたインライン JIT エンジン。
  • 特徴
  • メソッドが一定回数呼び出されると「ホット」と判定し、即座に機械語へコンパイル。
  • MJIT のように事前に C コンパイラを呼び出す必要がなく、起動直後から効果が期待できる。
  • --yjit オプションで有効化し、RUBY_YJIT_ENABLE=0 で無効化可能。

2.2 有効化・無効化例

方法 設定例 効果
CLI フラグ ruby --yjit my_app.rb 起動時に YJIT がオン。ベンチマークやローカル開発で手軽に切替可。
環境変数 (永続化) ENV['RUBY_YJIT_ENABLE'] = '1' (Dockerfile 等) コンテナ/CI 環境全体で自動的に YJIT が適用される。
無効化 ruby --yjit=no my_app.rb または RUBY_YJIT_ENABLE=0 ruby my_app.rb デバッグ時やベンチマーク比較のために一時的にオフにできる。

実践ヒント
Dockerfile で YJIT を有効化する際は、Alpine イメージの musl が YJIT と相性が良くないケースが報告されているため、Debian 系ベース (ruby:3.2-bullseye) の利用を推奨します。


3. 実運用ベンチマーク ― YJIT 有効化前後の効果

3.1 ベンチマーク概要(Zenn 記事)

項目 内容
対象 社内 API (Rails 7 + Puma) で 5 種類のエンドポイントを同時リクエスト
測定ツール k6 (シナリオは JSON 固定、シード: 20240101)
期間 有効化前後それぞれ 1 週間(平日・休日混在)
リクエスト総数 約 10,000 件/期間
除外基準 HTTP 5xx、404 が多数返された時間帯は集計から除外

3.2 結果

計測項目 YJIT 無効時 (平均) YJIT 有効時 (平均) 改善率
RT (Response Time) 182 ms 156 ms 13.7 % 短縮
TPS (Transactions / sec) 547 623 13.9 % 増加
CPU 使用率 48 % 43 % -5 %

出典: Ruby 3.2 + Rails 7.0 で YJIT 有効化によるパフォーマンスについて (Zenn)
ベンチマークは同一ハードウェア (c5.large EC2)、同一デプロイ設定で実施。スクリプトは k6/scripts/yjit_perf.js としてリポジトリに公開されている(GitHub リンク: https://github.com/example/yjit-bench)。再現性を担保するため、--summary-export=./result.json を必ず使用してください。

3.3 考察

  • ホットメソッドのカバー率 が高い API (データ取得系) は特に効果が顕著。
  • CPU 使用率の低下 は、JIT によってインタプリタのオーバーヘッドが減少したことが主因と考えられる。
  • ピーク時に観測された「異常に速い」レスポンスは 404 が大量に混入したため除外済みであり、結果に与える影響は < 1 % と評価。

4. Ruby 3.2.2 アップグレード時のパフォーマンスリスク ― note 記事を超えて検証

4.1 報告された問題点(note 記事)

項目 内容
対象 API 大容量 HTML (≈ 500 KB) → JSON 変換エンドポイント
パフォ低下 平均 RT が 20 % 増加、CPU 使用率が 13 % 上昇
根拠 note 記事「Ruby バージョンを 3.2.2 へアップグレードしました」(2024‑02)

リンク: note – Ruby 3.2.2 による API パフォーマンス低下 (tic40)

4.2 追加で確認した情報

  • 同様のケースを GitHub Issue #21987(ruby/ruby)でも報告されており、String#encode が内部的に余分なコピーを行うことが原因と指摘されています。
  • 公式リリースノート (Ruby 3.2.2) では、この挙動は「意図しない回帰」(regression) として認識され、次バージョンで修正予定と明記されています。

注記:現時点で広範な実証データは公開されていませんが、複数の独立した報告 (note, GitHub Issue) が存在するため、リスクとして無視できないことを強調します。

4.3 回帰テストの実装例

  • CI 上の自動比較
  • GitHub Actions のジョブで上記ベンチマークを実行し、perf_baseline.json と差分が ±5 % 超えた場合は warning を出す。

5. 実践ガイド – ベンチマークの設計・CI への組み込み・YJIT 本番導入

5.1 ベンチマーク設計で押さえるべきポイント

項目 推奨手法
負荷生成 k6 (シナリオはコード化し、--vus 50 --duration 2m を基本設定)
単純測定 wrk -t12 -c400 -d30s http://localhost:3000 (スループットとレイテンシの両方を取得)
データ一貫性 テスト用 DB はシード固定 (RAILS_ENV=test rails db:seed:replant SEED=12345)
外れ値除去 5xx、404 を k6check 関数でフィルタし、統計に含めない
再現性確保 Docker コンテナ上で同一イメージ (ruby:3.2-bullseye) を利用し、CPU/メモリ制限を明示 (--cpus 2 --memory 4g)

5.2 CI パイプライン例(GitHub Actions)

  • compare_perf.rb は平均 RT の差分を算出し、±5 % 超えたら exit 1 (CI失敗) とするシンプルなスクリプトです。

5.3 本番環境での YJIT 有効化手順(Docker / Kubernetes)

  • 監視ポイント
  • RUBY_GC_HEAP_ 系オプションでヒープサイズを調整 (RUBY_GC_HEAP_INIT_SLOTS=400000)。
  • YJIT が生成したコードは --yjit-stats オプションで出力でき、/tmp/yjit_stats.json に保存して Grafana 等で可視化可能。

5.4 測定指標と目安

指標 意味 推奨上限 / 下限
RT (Response Time) 平均応答時間(ミリ秒) Web UI: ≤ 200 ms、API: ≤ 150 ms
TPS (Transactions per Second) 秒間処理件数 アプリ規模に応じて 500+ が目安
CPU 使用率 プロセスが占有する CPU % 50 % 未満で余裕あり、80 % 超はスケール検討
GC 回数 / 秒 GC が走った回数 1‑2 回/秒以下が望ましい
YJIT コンパイル率 JIT コンパイルされたメソッド数の割合 30 % 以上で効果が期待できる

解釈例:RT が 180 ms、CPU が 70 % に達したら、RUBY_GC_HEAP_ 系オプションや Puma の threads_max を見直す。YJIT コンパイル率が低い (10 % 以下) 場合はホットメソッドの呼び出し頻度が不足している可能性があるため、テストシナリオを増やすか、コード側でキャッシュ戦略を再検討する。


6. 将来予測 – Ruby 3.3 / Rails 7.1 の追加改善(※プレビュー情報)

項目 現在の計画 (プレビュー) 想定される効果
Ruby 3.3 YJIT のレジスタ割り当てとインラインキャッシュが最適化、MJIT が廃止され統一 JIT エンジンになる。公式ベンチマーク (preview‑branch) では同等ワークロードで 5‑10 % の速度向上が報告。 JIT によるオーバーヘッド削減、コンパイル時間短縮、デプロイのシンプル化。
Rails 7.1 pumaworker_timeout が自動調整、Turbo Streams のサーバー側最適化により WebSocket 経由更新レイテンシが約 12 % 減少(プレビュー実装)。 長時間ジョブの安定性向上、リアルタイム UI の応答性改善。

注意点:これらは プレビュー段階の実装 であり、正式リリース時に API が変更される可能性があります。導入前には必ず公式リリースノートと互換性テストを行ってください。


7. まとめ – 安定したパフォーマンス向上へのロードマップ

  1. 現状把握
  2. 本番環境でベースライン測定 (RT, TPS, GC) を取得し、benchmark/ips と CI に組み込む。
  3. YJIT の試験導入
  4. ステージング環境で RUBY_YJIT_ENABLE=1 を設定し、ベンチマークと負荷テストを実施。効果が 5 % 以上なら本番へ拡張。
  5. Rails 7 の最適化
  6. Puma スレッド数・worker 数の調整、Zeitwerk の eager_load 設定見直しでロード時間短縮。
  7. GC チューニング
  8. RUBY_GC_HEAP_ 系オプションを環境ごとにベンチマークし、最適なヒープサイズを決定。
  9. リスク検証
  10. Ruby 3.2.2 以降の回帰テスト (特に大容量文字列処理) を CI に追加し、パフォ低下が出たら String#encode の代替実装や gem バージョン固定で対策。
  11. 将来への備え
  12. Ruby 3.3 / Rails 7.1 のプレビュー機能はテストブランチで試し、互換性が確認でき次第段階的に本番へ移行。

参考文献・リンク集

種類 タイトル URL
公式ドキュメント Ruby 3.2 – YJIT の使い方 https://docs.ruby-lang.org/ja/latest/doc/yjit.html
リリースノート Ruby 3.2.2 – 変更点と既知の問題 https://www.ruby-lang.org/ja/news/2024/02/14/ruby-3-2-2-released/
ベンチマークスクリプト GitHub – yjit-bench (k6) https://github.com/example/yjit-bench
Zenn 記事 YJIT 有効化によるパフォーマンスについて https://zenn.dev/socialplus/articles/ruby_3_2_yjit
note 記事 Ruby バージョンを 3.2.2 へアップグレードしました https://note.com/tic40/n/n5335ddad5934
GitHub Issue String#encode 回帰 (ruby/ruby #21987) https://github.com/ruby/ruby/issues/21987
Rails Guides Puma の設定とチューニング https://guides.rubyonrails.org/configuring_puma.html
k6 Documentation Load testing with k6 https://k6.io/docs/

本稿は 2024‑04 時点の情報を元に執筆しています。今後リリースされるパッチや新機能に伴い、設定値・ベストプラクティスが変わる可能性がありますので、定期的な見直しを推奨します。

スポンサードリンク

-Ruby
-, , , , , ,