こんにちは。エムスリーキャリアでエンジニアをしているakitoshigaです。
まだリリース前ではありますが、直近でレガシーなRailsアプリのアップグレードを実施したのでそのことについて紹介します。
弊社には約10年前にRailsで構築されたメディアサーバーが存在しています。
このメディアサーバはWebAPIによって画像ファイルやドキュメントファイルのアップロード・ダウンロードが行えるようなシンプルなもので特定のサービスにおける社内・社外のファイル共有のために使用されていました。
アップグレード前はそれぞれ以下のバージョンでした。
過去に一度アップグレードを施されたものの、以降一度もアップグレードがなされていないような状況でした。
最近このメディアサーバーを社内の他のサービスでも使用したいと考えらえるようになり、それを機に今回アップグレードに至りました。
アップグレード後のバージョンはそれぞれ以下です。
Railsにいたってはメジャーバージョンを2つ上げる大幅なアップグレードを実施しました。
アップグレードの流れ
Railsガイドのアップグレードガイドを参考にしつつも、以下のようにしました。
1. Gemのアップグレード
2. Railsを6.1.0にアップグレード
3. Rubyを3.3.1にアップグレード
4. Railsを7.1.3にアップグレード
5. Gemのアップグレード
本来であればもっと細かい粒度でインクリメンタルにアップグレードを行うべきですが、Railsはバージョン6を挟んで2回のアップグレード、Rubyは1回のアップグレードで目標バージョンに到達させました。
このようにした理由としては以下です。
- 小規模なシステム(モデル数9個)であり機能がシンプルでデバッグが比較的容易だった
- リリースノートを確認したところクリティカルに影響する変更がなかった
- 後述の
new_framework_defaults_*.rb
はメジャーバージョン事に対応したかった - あまり時間がなかった...
以下で詳しく解説します。
1. Gemのアップデート
まず最初に現状のGemを精査して使われていないものは削除しました。
その上で現状のバージョンで導入できるもっとも最新のGemにアップデートしました。
デバッグを行い現状のRails、Rubyに対応していないGemはバージョンを戻して固定しました。
バージョンを固定したGemは後の工程で最新にします。
2. Railsを6.1.0にアップデート
Gemfileのrails
のバージョンを6.1.0に固定してbundle update rails
を実行。
その後bundle exec rails app:update
を実行しました。
対話形式で各種設定ファイルを上書きするかそのままにするかを設定しますが、以下のようにしました
- 一度すべての設定ファイルを上書きする
git diff
で差分を見て、過去手動で設定した部分をマージしていく
lazygitは行単位で変更を取り消せるのでこの作業を行うのに便利でした
bundle exec rails app:update
を行うとconfig/initializers
配下にnew_frame_work_defaults_*.rb
が生成されます。
これはRailsのマイナーバージョン以上をアップグレードするたびに生成されるファイルで、Railsのデフォルトの設定の更新差分が記載されています。
*
の部分はアップデートされたときのバージョンが入るので今回はnew_frame_work_defaults_6_1.rb
となります
生成された直後はコメントアウトされており、コメントを解除していくことで設定が適用されていきます。
ただし、これらの設定をすべて適用しても良い場合はconfig/application.rb
の項目
config.load_defaults
の設定値をアップデートしたバージョンに指定すればnew_frame_work_defaults_*.rb
をすべてコメントアウトしたのと同じ状態
になります。
new_frame_work_defaults_*.rb
を個別で管理する必要はないので以下のようにしました。
1. インクリメンタルにコメントを外していく
2. 影響のあるものはconfig/application.rb
や他の設定ファイルに記載
3. config.load_defaults
の値を変更(今回は 6.1)
4. new_frame_work_defaults_*.rb
を削除
3. Rubyを3.3.1にアップデート
開発環境のDockerfileで定義されているRubyのバージョンを3.3.1に変更しました。
このタイミングでbundlerもアップデートしました。
アップデートしたbundlerでGemfile.lock
を出力する必要があるので以下を実施しました。
bundler update --bundler
幸いバグらしいバグが出なかったのですぐに完了しました。
4. Railsを7.1.3にアップデート
6.1.0にアップデートした時と同様の流れでアップデートを実施しました。
さすがにメジャーバージョンを2つ上げると使用していたGemが現在のバージョンに対応できなくなったりDeprecatedの警告が多くなってきます。
使用できなくなったGemや、警告されている部分の対応を行いました。
4. Gem update
バージョンを固定していたGemを最新に変更しました。
発生したエラーとその解決
他の大規模なRailsアプリに対してアップグレードは比較的容易だったとはいえRailsやGemの依存関係に起因するエラーはかなり複雑でした。
多くのエラーは先人がGitHubのIssueやブログにまとめたりしてくれていますが、そうでない物はGemをcloneして特定のメソッドに対してバージョンごとに差分を確認していくような作業も行いました。
まとめ
アップグレードはこまめにやったほうが良いとは思うものの、品質の向上・担保における取り組みはプロダクトのフェーズによってはどうしても後回しになってしまいがちだと感じています。
定期的にアップグレードを行えるような仕組みづくりの重要さを実感しました。