freeeの開発情報ポータルサイト

大規模Railsアプリを支える「バックエンド委員会」とRuby YJIT導入によるパフォーマンス改善

こんにちは!フリー株式会社でエンジニアをしているyassyとkenzaです。

2026年1月30日に開催された「freee Tech Night」にて、「freee会計の大規模開発を支える "バックエンド委員会"」というテーマで登壇しました。

www.youtube.com

freeeの基幹プロダクトである「freee会計」は、リリースから14年が経過した国内でも有数の大規模Ruby on Railsアプリケーションです。本記事では、この巨大なプロダクトをいかにして健全に保ち、進化させ続けているのか、その中核を担う「バックエンド委員会」の活動と、直近で取り組んだYJIT導入の詳細について解説します。

1. 巨大なモノリスと14年の歴史:freee会計の現状

freee会計のバックエンドは、2012年7月の最初のコミットから今日まで、膨大な機能追加と修正を繰り返してきました。そのため、以下のような課題があります。

  • 開発規模: 毎週1,000以上のコミットが積み重なり、非常に多くのチームとエンジニアが同時に開発に関わっています。
  • 複雑なドメイン構造: 長年の開発により、会計のコアロジックと周辺の業務ドメインが密結合しており、システム全体が巨大なモノリスとなっています。
  • 技術負債の蓄積: 14年前のベストプラクティスが、現代のエンジニアリングにおいては逆に負債となってしまうケースも少なくありません。

2. 横断組織「バックエンド委員会」の役割

こうした技術的課題に対し、組織横断で改善を推進しているのが「バックエンド委員会」です。

委員会の2つのミッション
  1. 開発生産性の向上: 開発者が複雑な環境に惑わされず、素早く価値を届けられる状態を作る。
  2. 保守性・品質の向上: 10年後もメンテナンス可能なコードベースを維持し、障害に強い基盤を作る。
多様なメンバーによる草の根活動

この委員会の特徴は、参加の敷居が極めて低いことです。Rubyやバックエンドの知識に精通したエキスパートだけでなく、配属直後の新卒エンジニアも積極的に参加しています。

実際に、筆者の一人であるkenzaは、配属時にRuby未経験でしたが、Ruby歴3ヶ月の状態で委員会に参加しました。

普段は別々のチームでプロダクト開発をしているメンバーが集まり、組織横断的に兼務で改善タスクをこなしています。

3. 生産性と品質を高めるための具体的な取り組み

バックエンド委員会が行ってきた主な取り組みを紹介します。

コードの治安維持:フォーマッタ/リンター(Syntax Tree, RuboCop) の整備

コードの品質を一律に保ち、実装やコードレビューでの負荷を最小限にするために導入しています。

  • メリット: 開発者は本質的なロジックの開発に集中でき、レビューに対するコストが大幅に下がりました。
  • 課題: 導入時に作成した「rubocop_todo(後回しにしたルール)」が肥大化している点です。これを委員会で計画的に解消する仕組み作りを進めています。
型の導入:静的型チェッカー(Sorbet) / 型定義生成ツール(Tapioca) の普及

Rubyのような動的型付け言語において、大規模開発の安全性をもたらすのが静的型チェッカーのSorbetです。

  • 取り組み: GitHub Actionsやテスト実行時のランタイムで型チェックを自動化。
  • 効果: 型定義ファイル(RBI)の差分チェックにより、予期せぬ実行時エラーを未然に防げるようになりました。また、入出力の型が明確になることで可読性が向上しました。
開発サイクルの高速化:CI(継続的インテグレーション)の改善

1,000件/週のコミットを捌くためには、CIの高速化が命題です。

  • 具体策: RuboCopを実行する際に変更差分のみを対象にしたり、テストデータのシード値をキャッシュ化するなどの工夫をしています。
  • 地道な修正: 時折失敗するflakyなテストの撲滅や、実行時間の長いRSpecのチューニングを継続的に行っています。

4. Ruby YJIT 有効化によるパフォーマンス改善

今回のメイントピックが、Ruby 3.1から搭載されたJITコンパイル機能「YJIT (Yet another Ruby JIT)」の導入です。

YJIT導入の動機

大規模アプリケーションにおいて、ロジックの改善だけでパフォーマンスを劇的に上げるには限界があります。YJITは、実行時にRubyコードをマシンコードへ変換・キャッシュすることで、アプリケーション側のコード変更なしに速度向上を狙える、コストパフォーマンスの高い手法です。大規模でパフォーマンス改修が困難なfreee会計では一定の効果が見込めそうだったので、導入に至りました。

事前調査とリソース計画

YJITはメモリを多く消費することが分かっていたので、以下の観点で調査を進めました。

  • リソース見積もり: KubernetesのPod単位で、YJITで消費する分上乗せされるメモリリミットやワーカー数を試算しました。
  • OOMKilled対策: YJITが生成するコードのサイズやメタデータによるメモリ増加分を見積もり、Podのメモリ制限をあらかじめ調整しました。
検証からプロダクション導入まで
  1. staging環境での負荷テスト: HPA(自動スケーリング)をあえて停止し、試験環境に一定の負荷をかけて集中的にメトリクスを計測しました。
  2. 独自メトリクスの監視: code_region_size(生成コードサイズ)や ratio_in_yjit(YJIT実行率)をロギングし、期待通り動作しているか確認しました。
  3. 手動カナリアリリース: 本番環境では、いきなり全台に適用するのではなく、一部のPodから段階的に反映しました。異常がないことを確認しながら展開し、万が一の際のロールバック体制の整備も行いました。
導入成果
  • レスポンスタイム: APIにおいて、平均・中央値で約15%もの高速化を確認。さらに、遅いリクエスト(P90〜P99)でも約13%改善しました。
    YJIT導入後のレスポンスタイムのグラフ
    YJIT導入後のレスポンスタイム
  • CPU使用率: 全体で約20%低下。以下のようにCPU使用率が大幅に減り、リソース効率が向上しました。
    YJIT導入後のCPU使用率のグラフ
    YJIT導入後のCPU使用率
  • メモリ増: 想定通りメモリ消費は増えましたが、リソース計画の範囲内であり、安定稼働を続けています。
    YJIT導入後のメモリ使用率のグラフ
    YJIT導入後のメモリ使用率

5. 今後の展望:ZJITへの備えとAIの活用

次世代JIT「ZJIT」を見据えた設計

Ruby 4.0から導入されたZJITは、メソッド単位でのコンパイル方式に移行します。現在はまだYJITの方が高速ですが、将来的に採用していく可能性があります。

  • 型の安定性: ZJITを最大限活かすには、実行時に型が変動しない設計が重要です。Sorbetなどを活用することで型安全なコードを書くことが、将来のさらなる高速化にも繋がると考えています。
AIによる負債解消の自動化

今後は、AI(LLM)を活用して技術負債を自動で検出し、優先順位付けを行う仕組みを構想しています。

  • 調査や定型業務のSkills化: 修正案をAIが提示し、委員会がそれを承認してデプロイするような、AIがオーナーシップを持つ効率的なメンテナンス体制を目指します。
  • CLAUDE.mdの整備: AIでコーディングを行った際に、自動でフォーマットや型をチェックしてテストを実行する仕組みを整備しています。

おわりに

freee会計のような巨大なプロダクトにおいて、技術基盤を健全に保つのは簡単なことではありません。しかし、バックエンド委員会のように、メンバーが自発的に「自分たちの環境を良くしよう」と動く文化こそが、freeeのエンジニアリングの強みです!