Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Railsのアップグレードを成功させるための知見リスト(翻訳)

概要

元サイトの許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。

参考: Rails アップグレードガイド - Railsガイド

Railsのアップグレードを成功させるための知見リスト(翻訳)

最近、私たちは年季の入ったさまざまなプロジェクトでコンサルティングやアップデート作業を行っていました。どのプロジェクトもproductionで運用されていてビジネスを回していますが、長年アップグレードされないまま放置されていました。

こうしたプロジェクトでの経験を活かして、アップグレード作業をスムーズにするための知見を本記事でいくつか共有したいと思います。

🔗 アップグレード前にやるべき作業

🔗 依存関係をできるだけ減らしておく

作業を後々シンプルにするために、私は真っ先にGemfileを監査することにしています。

今の時代で用無しになったgemがあるかどうかを調べてみると、たいていいくつか見つかるものです。私が特にチェックするのは以下のようなgemです。

  • 1: コードからまったく参照されていないgem

しかし、通常とは異なる使われ方をするgemがたまにあるので、この手のgemについても油断せずに目を光らせておくべきです。そうしたgemは、RubyやRailsのクラスを拡張したりパッチを当てたりしている可能性もあります。私たちの場合、たとえばactive_model_serializers gemのせいでつらい思いをしました。

  • 2: インラインでコードを書けば使わなくても済むgem

たとえば、長年メンテされていない巨大なgemがあり、自分たちがそのgemのたった5行のメソッドしか使っていないのであれば、そのコードをコピペして使う方がよいでしょう。アップグレード作業が終わりに近づいた段階でこの手のgemが問題を起こす心配はありません。

  • 3: フレームワークにも同じ機能があるgem

activerecord-import gemが典型例です。Rails 6より前の時代は重宝しましたが、Rails 6以降を使っていれば組み込みの機能で同じことができます。

別の例としてはaasm gemがあります。これについては同僚のSzymonがRails組み込みのenumに置き換える方法を素晴らしい記事にまとめてくれました。

Rails: aasm gemは今すぐRailsの新しいenumに置き換えよう(翻訳)

🔗 セキュリティ問題を軽減しておく

CVE(common vulnerabilities and exposures)のセキュリティ情報についても必ずチェックすることにしています。

私の場合はbundler-audit gemで既知のセキュリティ問題があるかどうかをチェックしています。bundler auditコマンドを実行すると、脆弱性のリストと重大レベル、修正方法の推奨事項が表示されます。ここで重大な問題が見つかった場合は、アップグレードの前に対処を済ませておきます。

🔗 非推奨警告を必ず収集する

Railsで表示される非推奨警告に対処することはかなり一般的になってきましたが、Rubyそのもので表示される非推奨警告を適切に処理する体制が組まれているプロジェクトはほとんど見かけません。

Rubyの非推奨警告を処理する方法については以下の記事に書きました。

Rails: Rubyの非推奨警告はデフォルトで表示されないことを見落としていませんか?(翻訳)

🔗 アップグレードで頼りにすべき地図

🔗 一般的なセマンティックバージョニング

ソフトウェアを提供する組織は、セマンティックバージョニング)(SemVer)のルールに従っているのが普通です。

MAJOR.MINOR.PATCH形式のバージョン番号は、それぞれ以下の場合に値を増やすこと。

  • MAJORバージョン: APIで破壊的変更を行った場合
  • MINORバージョン: 後方互換性のある機能を追加した場合
  • PATCHバージョン: 後方互換性のあるバグ修正を行った場合

実際には、アップグレードするバージョンに応じて、アップグレードで何が起きるかを知っておく必要があります。

🔗 Railsのセマンティックバージョニング

残念ながら、現実のバージョニングの実施方法は組織ごとにまちまちです。

たとえばRailsはセマンティックバージョニングの"シフト版"に沿っていて、MINORバージョンにAPIの破壊的変更が含まれる可能性があります。こうした破壊的変更については、直前のMINORまたはMAJORリリースで事前に非推奨化を通知するようになっています。RailsにおけるMINORリリースとMAJORリリースの違いは、破壊的変更の規模の大きさの違いです。

Rails 4.0.11.1以降、Railsチームはバージョン番号に4つのコンポーネントを含むバージョンをリリースすることがあります。このリリースが最初に行われた背景には、Rails 4.0.12に重大なセキュリティ修正を含めてリリースされたという話がありました。ただし、このRails 4.0.12には、セキュリティ問題解決に必要な変更以外の変更も組み込まれていました。Railsチームは、誰もがリグレッションの恐れなしにセキュリティ修正パッチを適用できるように、セキュリティ修正だけを含む追加リリースも別途提供したのです。

🔗 高レベルのアップグレード計画

ここでは、「現在Rails 6.0.2を使っていて、Rails 7.1.3.4(記事執筆時点の最新版)へのアップグレードを目指している」シナリオを考えてみましょう。

最新のRailsバージョンと現状のRailsバージョンの間には94件ものリリースが行われています。94件分をエイヤで一度にアップグレードしますか?それとも最大限の安全性を確保するために、面倒でも94件のアップグレード手順をスキップせずに1つ1つ実行する方にしますか?

私にはどちらの方法もいいとは思えません。私たちの場合に有効な戦略は、「マイナーバージョンを1つずつ進める」「常に最新のパッチバージョンを適用する」の2本立てです。

今回のシナリオでは、以下の4つのアップグレード手順が必要になります。

  • 6.0.2 -> 6.0.6.1
    MINORバージョンを次に進める前段階として、現状のMINORバージョンにおける最新のPATCHバージョンだけを適用する
  • 6.0.6.1 -> 6.1.7.7
    MINORバージョンをアップグレードする
    (最新バージョンのPATCHがあればそれも適用する)
  • 6.1.7.7 -> 7.0.8.1
    MAJORバージョンをアップグレードする
    (最新バージョンのPATCHがあればそれも適用する)
  • 7.0.8.1 -> 7.1.3.4
    MINORバージョンをアップグレードする
    (最新バージョンのPATCHがあればそれも適用する)

アップグレードの各手順で、アプリケーションで新しく問題が発生していないかどうか監視し、非推奨警告をひととおり収集して修正してから、次の手順に進むようにしています。

🔗 低レベルのアップグレード計画

現場でのアップグレード作業は、単にバージョンを別のバージョンに上げるだけの作業よりも複雑になるものです。どんな作業が必要になるかは、アップグレード作業を実際に開始してみるまでわからないことも多々あります。

たとえばRailsをアップグレードするときの現実の作業を考えてみましょう。

GemfileでRailsのバージョンを上げたものの、bundle installを実行すると失敗し、xというgemを先にアップデートしておかなければならないことが判明したとします。
そこでbundle installを再度実行したところ、またしても失敗しました。今度は、x gemをアンロックするために、yというgemを先にアップデートしておかなければならないことが判明しました。
しかしy gemをアップデートするには、その前にコードを書き換えないといけないことが判明...といった具合です。

最終的にbump-railsブランチには、アップグレード準備用のコミットが十数個も入ってきます。Railsバージョンが実際に変更されるのは、その中の最後のコミットです。さて、ここからどうしますか?こいつをmainブランチにマージする勇気はありますか?

私たちArkencyでは、このような進め方はしません。
このようなアップグレード準備用のコミットは、必ずmainブランチに1個ずつバックポートし、重要な変更が発生したらその都度デプロイするようにしています。アップグレードに必要な変更がmainブランチに全部入ったら、bump-railsブランチをmainブランチにrebaseする準備が整ったことになります。

こうすることでコミット数が1〜3個に削減され、レビューとマージがやりやすくなります。私たちは常に、簡単に戻せる一口サイズの変更を行うように心がけています。

🔗 Ruby Standard Gemsサイトの情報を活用する

Rubyをアップグレードするときに、アップグレード範囲を細かなステップに分割する方法も使えます。

Rubyには、インタプリタにバンドルされる標準ライブラリのセットが付属しています。これらはGemfileに含まれていない可能性もありますが、アプリケーションがそれらの振る舞いに強く依存している可能性は非常に高くなります。

標準ライブラリのセットは、Rubyバージョンごとに異なっており、ライブラリの一部が削除・追加・更新されることがあります。Rubyをアップグレードしたときにどのライブラリが影響を及ぼすかについては皆さんでも簡単に調べられると思います。私の場合は、stdgems.orgというサイトで確認しています。

参考: Standard Gems -- stdgems.org

Rubyの次期バージョンでライブラリに重要な変更が発生することが判明したら、Ruby本体より先にライブラリを更新するようにしてください。これはGemfileで明示的に記述する必要がありますが、その価値はあります。こうすることで、Ruby本体のバージョンをスムーズに変更できるようになります。

🔗 お知らせ: ご相談に乗ります

RubyやRailsのアップグレードで苦戦している方は、ぜひ私たちArkencyのフォームまでお気軽にご相談をお寄せください。

私たちは、さまざまな規模、さまざまな複雑さのレベルにわたるアプリケーションのアップグレード経験を積んでおり、アップグレードをスムーズかつ楽に行うお手伝いをいたします。

関連記事

Rails: Rack::Deflaterの条件付きGZIP圧縮でレスポンスサイズを劇的に削減(翻訳)

Rails: aasm gemは今すぐRailsの新しいenumに置き換えよう(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。