🧑‍💻

入社前だけど3,000万MAUのサービスをRails8.0にアップグレードしちゃいました

2025/03/25に公開2

はじめに

株式会社マイベストに2025年度入社予定のYagiです!一年前からバックエンドエンジニアとして内定者インターンに取り組んでいます。今回、マイベストで使用されているRuby on Railsを7.2から8.0にアップグレードしたので、その手順や遭遇した問題点などをまとめています。

アップグレード手順

  1. Gemのバージョンを最新にする
  2. アップデートタスクを実行
  3. 新たに発生するようになったエラーへの対処
  4. フレームワークのデフォルトを設定
  5. QA環境にデプロイして動作確認する
  6. 本番環境にデプロイ

Gemのバージョンを最新にする

Railsのアップグレード作業に入る前に、Rails以外のGemバージョンを最新にします。これは、Rails本体の変更だけでなく、依存するGemの変更による影響も最小限に抑えるためです。

マイベストでは、Dependabotを導入し、普段からGemの依存関係を管理しています。そのため、差分もあまりなく、比較的スムーズに最新バージョンへの更新作業を行うことができました。
各GemのChangelogで破壊的変更を確認し、プルリクエスト上でも表にまとめておきます。

開発環境・テスト環境のGemと本番環境のGemでリリースを分けることで、リリース時の影響を最小限に抑えました。

bundle update -g development -g test

アップデートタスクを実行

Gemのアップデートが完了したので、Railsを8.0に上げていきます。

- gem 'rails', '~> 7.2'
+ gem 'rails', '~> 8.0'
$ rails app:update

設定ファイルなど、手作業での修正が必要な箇所は、Rails Diffなどのサイトで差分を確認しながら慎重に作業し、意図しない変更や設定ミスを防ぎました。

新たに発生するようになったエラーへの対処

Railsのバージョンアップに伴って、新たなエラーや警告が追加されたので、該当箇所を修正します。

ルーティングエラー

↓のリリースにより、ルーティング設定において、存在しないメソッドをonlyまたはexceptオプションで使用した場合、ArgumentErrorが発生するようになったようです。

https://github.com/rails/rails/issues/53269

エラーメッセージに従ってonlyオプションを書き換えました。

-  resource :user, only: :none do
+  resource :user, only: [] do

RSpecエラー

次に、CIのRSpecテストでは2種類のエラーが大量に発生していました。

↓のリリースにより、STATS_DIRECTORIESの定義方法が変わったことで、2回目以降の実行時はエラーを吐くようになりました。
https://github.com/rails/rails/pull/52226

挙動をコンソールで確認


そして、このSTATS_DIRECTORIESが定義されているファイルはtasksディレクトリ内に配置されていたため、タスクの過剰な読み込みが発生していると予測。

確認したところ、各テストグループの開始前に毎回タスクがロードされていました。
テストスイートの開始時に一度だけ行えば良いので、before(:suite)に移します。

spec/spec_helper.rb
  config.before(:suite) do
+    Rails.application.load_tasks
  end

  config.before(:all) do
-    Rails.application.load_tasks
  end

やはりこれが原因でした。

また、各テストファイルでもさらにタスクをロードしてしまっていたので、まとめて削除します。

spec/lib/tasks/hoge_spec.rb
RSpec.describe 'hoge_task' do
-  before(:all) do
-    @rake = Rake::Application.new
-    Rake.application = @rake
-    Rake.application.rake_require 'tasks/hoge'
-    Rake::Task.define_task(:environment)
-  end
  # ...
end

フレームワークのデフォルトを設定

Rails8.0では新たに3つのデフォルト設定が追加されました。

Rails.application.config.active_support.to_time_preserves_timezone = :zone
Rails.application.config.action_dispatch.strict_freshness = true
Regexp.timeout = 1

どれも軽微な変更ですが、対応が必要ないことが確認できたらload_defaultsを上げて設定を適用します。

config/application.rb
class Application < Rails::Application
  # Initialize configuration defaults for originally generated Rails version.
-   config.load_defaults 7.2
+   config.load_defaults 8.0
  # ...
end

また、レビューしやすいよう、これらの設定について概要や挙動の違いをNotionにまとめました。

↓詳細はこちら

デフォルト設定の変更点

1. active_support.to_time_preserves_timezone = :zone

概要

to_time メソッドがタイムゾーンを保持するか、UTCオフセットを保持するかを制御します。

設定オプション:

  • :zone: タイムゾーンを保持
  • :offset: UTCオフセットを保持
  • false: ローカルシステムのUTCオフセットに変換

以前までデフォルトで :offset だったのが、8.0で:zone になりました。
https://edgeguides.rubyonrails.org/configuring.html#config-active-support-to-time-preserves-timezone

コンソールでの両者の挙動:

:offset (7.2 以前)

:zone (8.0 以降)

今後はTimeオブジェクトからタイムゾーン情報も参照できるようになり、より正確な時間情報の扱いが可能になります。

2. action_dispatch.strict_freshness = true

概要

HTTP ヘッダーの If-Modified-SinceIf-None-Match に基づいてリソースが変更されたかどうかを判断する際の挙動を制御します。

設定オプション:

  • true:
    両方のヘッダーが存在する場合、If-None-Matchのみが考慮されます。
  • false:
    両方のヘッダーが存在する場合、両方のヘッダーがチェックされます。

https://edgeguides.rubyonrails.org/configuring.html#config-action-dispatch-strict-freshness
Rails 8.0以降では、RFC 7232に準拠するように、デフォルト値がtrueに変更されました。

3. Regexp.timeout ||= 1

概要

ReDoS 対策のため、 Regexp.timeout にデフォルトで1秒をセットします。
https://rubyonrails.org/2024/11/1/this-week-in-rails

正規表現に1秒以上かかる場合はタイムアウトエラーが発生するようになるので、必要に応じて設定値を変更する必要があります。

Regexp::TimeoutError: regexp match timeout

QA環境にデプロイして動作確認する

ローカル環境での確認とCIのテストにパスしたら、QA環境(テスト環境)で動作確認を行います。
マイベストでは月に一度、PdMによって定期点検を目的としたサービス全体の動作確認が実施されます。
今回は事前にPdMとコンタクトを取り、定期点検に相乗りして主要な機能を動作確認してもらいました。

本番環境にデプロイ

動作確認とコードレビューが完了したら、いよいよ本番環境にデプロイします!
問題をすぐに検知し、ユーザーやエンジニアへの影響を最小限にできるよう、全体に周知しておきます。

スタンプに背中を押され、いざデプロイ...!

無事、何の問題もなくアップグレードを完了できました。

まとめ

GemのChangelogを確認することが予想以上に地道で、アップグレード作業全体の半分の時間を充てることになりました。Dependabotを活用していなければさらに時間がかかっていたので、日頃からのアップデートの大切さを改めて感じました。

また、今回変更を加えた行数は、以下のようになりました。
Dependabotのおかげもあり、Gem関連の変更は最小限に抑えられました。

Pull Request 変更行数
Railsアップグレード
開発環境とテスト環境のgemアップデート
RSpecのリファクタリング
合計 +571 −700

個人的に「作業漏れなく確実にアップグレードをやり切る」「絶対にサービスを止めない」というスタンスで臨んでいたため、やり遂げられてホッとしました。徹底して調査したことで、最後は確かな自信を持ってリリースできました。

調査にあたっては、この3点を意識すると網羅的に進めることができます。

  • Gemの破壊的変更をリポジトリのChangelogで確認
  • Rails アップデートタスクの差分をRails Diffで確認
  • デフォルト設定の変更点をリリースノートで確認

Rails8.0は新機能の追加がメインであり、対応が必要な変更点は少なかったため、今後またアップグレードする機会があったら挑戦してみたいと思います。

参考資料

https://qiita.com/jnchito/items/0ee47108972a0e302caf
https://engineering.mobalab.net/2024/11/15/upgrade-to-rails-8-0/
https://qiita.com/aeroastro/items/c97bd26ce8b8818b6bed

Discussion

r-sugir-sugi

すごく分かりやすかったです!👏
gemのバージョンアップによるメトリクスへの影響(メモリ使用量減少とか)も知りたくなりました!

YagiYagi

ありがとうございます!
黒い縦線がリリースタイミングですが、今回はメトリクス上に大きな変化はなかったです。