🔖

バージョンアップ対応に苦労した過去の自分に送りたい7つの心得

2024/08/07に公開

こんにちは。
株式会社ココナラでバックエンド開発に従事するRKと申します。

みなさまはシステムのバージョンアップ対応をした経験はありますでしょうか?

システムの安定稼働に配慮して一定期間で実施している場合もあれば、利用しているライブラリや開発言語そのものの End Of Life(以降、EOL) によってバージョンアップを余儀なくされて実施した場合もあるでしょう。

どちらにせよ、ユーザーの皆様に安心してシステムをご利用いただくためにも、バージョンアップ対応はとても大事な作業の1つとなります。

弊社ココナラでも、もう少しでEOLを迎える/迎えた開発言語や環境を持つシステムは存在します。

本記事では、ココナラのとあるシステムのバージョンアップ作業対応を実施した際の私自身のふりかえり内容を記載します。
本当にこの記事を過去の自分に送って読ませたい。

なお、弊社ではすでにバージョンアップに関連する別の記事もあります。
合わせてそちらもお読みいただけると、おもしろいかもしれません。

https://zenn.dev/coconala/articles/1346310c984195

システム概要の説明

以下、バージョンアップ対象となったシステムの特徴です。

  • gRPCを導入したRuby on RailsのWebアプリケーション
    • ECS上でマイクロサービスとして動作。Fargeteタイプ。
  • Ruby/Railsのバージョン情報
    • Ruby : 2.5系 → 3.2.4
    • Rails : 5.2系 → 7.1.2
  • 取り扱う情報
    • 出品者のプロフィール情報
      • 本人確認情報、学歴、経歴、ポートフォリオ、興味のあるカテゴリ、など
    • ブログ関連情報(自身のブログの管理、ブックマーク、ブログ購入時の決済処理を含む)
  • 接続する外部サービス:
    • s3、GMO、eKYC
  • その他の特徴
    • Sidekiqの導入により、非同期処理あり
    • Elasticsearchとの連携あり
    • ECS上で動作するバッチとしても起動する

バージョンアップ作業で得られた知見

以下、実際にバージョンアップ作業をした時に得られた知見です。

  • 先人が残した「 バージョンアップを進める際の注意事項 」を確認しておきましょう。
  • バージョンアップ実施の計画を立てましょう。思うように進まない場合に、方向性の判断材料となります。
  • 立てた計画は、必ず上長や関係部署と共有しておきましょう。立てた計画はまず遅れます。
  • バージョンアップ対象機能の機能/特性を知っておきましょう。とくに「ログの内容」と「CPU/メモリ/ネットワークトラフィックなどの負荷状況」は大事。
  • 単体テストは可能な限り書きましょう。またバージョンアップ時にコード修正が発生したら、その部分のテストも追加しておきましょう。
  • 安全に進めるのは大事です。ですが、場合によってはある程度の後戻りも覚悟して、まとめてバージョンアップを進めてみましょう
  • 実施したことを記録として残しておくのは、ものすごく有効。

端的にまとめれば、公式のRailsアプリケーションのバージョンアップ記事やZennやQiitaに投稿されているバージョンアップの心得の記事を読み、その背景を理解しつつも自分達の事業でどのようなスケジュールで進めていくのかを確認/調整していくのが大事だという認識です。

それと、私個人としては、実施内容の記録を残しておいたことは正解だったなと考えています。
少なくとも、このテックブログを書く際のネタにすることができました。

なお、下の画像はesaに記載した、作業メモとなります。

figure_1

バージョンアップ作業のふりかえり

つづいて、バージョンアップ作業自体のふりかえりです。

バージョンアップ作業は以下の手順で実施しました。

  • 単体テストのカバレッジ向上
  • gemのバージョンアップ
  • Ruby/Railsのバージョンアップ
  • 検証環境下で実際の動作

「gemのバージョンアップ」と「Ruby/Railsのバージョンアップ」は相互に前後しながら実施していきました。
それぞれの作業について、もう少しだけ深掘りします。

単体テストのカバレッジ向上

ruby/Rails/gemのバージョンが上がることにより、同一メソッドでも微妙に挙動が変わったり、必須パラメーター/文法が変わる場合があります。
そのような各種変更に対して、業務ロジックがバージョンアップ前後で変わっていないよね、というのを確認するためにも単体テストは非常に重要です。
そして、その単体テストがどこまでのカバーをしてくれるかによって、バージョンアップ作業の信頼度は変わってきます。

理想は MCC(複合条件網羅)が100% です。
しかし、テストケースが多くなりすぎたり、テストを書くための準備がものすごく大変になったりもします。
ですので、必要最低限な部分だけを押さえて実施するのも大事です。
基本的には条件分岐に着目した C1(分岐網羅)で100% を(可能なら)目指し、複雑な条件が発生する部分のところだけ抜粋して C2(条件網羅)のテストも追加する 、といった形で単体テストを追加していきました。

幸い、対象になったシステムでは単体テストのコードは充実していました。
実績値として、97%程度のカバレッジ率を保持していました。
これなら、カバレッジ向上のタスクはそんなに時間がかからないかなと思っていました。
しかし、蓋を開けてみれば、ほとんどテストが書かれていないクラスやrakeファイル、一部の例外処理のテストが書かれていなかったりといった部分もあり、想定より時間を取られる結果となりました。

gemのバージョンアップ

Ruby/Railsのアプリケーションで、gemを利用しないことはまずありません。
そのため、まずは現状のRuby/Railsのバージョンで、gemのバージョンアップを適度に行っていきます。

この作業の理想は、一つひとつ丁寧にそれぞれのgemのバージョンをアップすることです。
RubyGemのサイトを見て、Rubyのバージョンと関連するgemのバージョンも見て、問題なければバージョンをあげ、テスト(rspec)を実行して問題ないか……を繰り返す。

ですが、gemは別のgemに依存しており、そのgemはまた別のgemを利用している状況。とにかくgemはたくさんあります。
ひとつずつじっくりとでは、時間だけがどんどん過ぎていきます。
そのため、主要なgemやトラブルが多く報告されているようなgem以外は、ある程度の機能の単位で一気に上げていきましょう。

figure_2

結局のところ、gemのバージョンをあげてみないと、動作するかどうかは判断できないです。
gitのようなバージョン管理システムがあれば、バージョンを上げ過ぎて正常に動作しなくなっても、元に戻すことができます。

失敗を恐れずに実施していきましょう。

余談ですが、意外とバージョンアップを失念しやすいgemが、bundlerです。
Rubyの標準に付属しているものを利用するのもいいですが、gemによってはbundlerのバージョン指定もあったりします。
そのため、適度なバージョンのbundlerにあげておくのが望ましいです。

Ruby/Railsのバージョンアップ

さて、いよいよバージョンアップ作業の正念場です。
多くのエラーと向き合うことになると思います。

RubyとRailsのバージョンアップで大事なのは、順番です。
RubyからあげるのかRailsからあげるのか。
メジャーバージョンを上げるのか、マイナーバージョンからあげていくのか、など。

当然、組み合わせることができないバージョン同士もありますので、どのようにアップしていけばいいかは事前に調査しておくのが良いでしょう。

なお、それぞれのバージョンをアップした推移は、以下となります。

  • Ruby: 2.5.1 → 2.6.10 → 2.7.8 → 3.0.6 → 3.1.4 → 3.2.3 → 3.2.4
  • Rails: 5.2.0 → 6.0.6.1 → 7.0.8 → 7.1.2

Rubyだけ上げたケース、Railsだけ上げたケース、同時にあげたケースなど、それぞれのケースがありました。
バージョンを上げたのはいいが、適用できないバージョンもありました。

figure_3

いざ、検証環境で動作確認

バージョンもあげ切り、単体テストがすべて通過したことも確認できました。
したがって、実際に検証環境へデプロイして動作確認となります。
検証環境上でのテストも、反省点は多かったです。

反省点の中で、代表的な例を2つ紹介します。

検証テストが失敗した:例1:単体テスト不足

ある機能が動かない!
バージョンアップ前は動いていた!

Rubyのバージョンの影響か?それとも、外部IFの変更か?gemの変更か?
というか、単体テストでは見つけられなかったので、この辺りが怪しい。
結局、ログをバンバン入れて、動作検証をする感じに。

RubyもGemも関係なかった。ただ、そこにテスト不足があった

結論、rubocopに指摘された箇所を直したのですが、その直し方が間違っていたというオチでした。
また、単体テストが通ったと安心していましたが、蓋を開けてみたら、C1レベルでの分岐テストが書かれていなかった箇所でもありました。
ここの分岐のテストさえ書いてあれば、単体テストレベルで十分に改修できたレベルだったのに。

判明した時、思わずガックリしてしまいました。

検証テストが失敗した:例2:テスト方法の不足

こちらは準備不足の類に該当します。
ある特殊な機能の動作確認をする際に、事前に実施方法を確認できていなかったパターンです。

検証テストを実施する際に、1名助っ人に来てもらいました。
検証テストのリスト自体は用意してあったのですが、そのテスト実施方法までは調べ切れていませんでした。

そのため、助っ人の方に、テスト実施方法を機能作成者に確認してもらうということになりました。

figure_4

幸い、助っ人の方が担当者の方とうまく調整/ヒアリングしてテストを実施してくれたのですが、作業依頼者としては、もう少し準備をしておいた方が良かったなと反省しました。

バージョンアップ作業時に送りたい7つの心得

私が今回のバージョンアップ対応でやらかしてしまった失敗を参考にして、もしも過去の自分に対してアドバイスできるのであれば、以下の7つの心得を送りたいと思います。

心得1: 後戻りが怖くても、最初に勇気を出して先に進もう

「この程度の規模で、なんでこんなに時間かかっているの?」

言い方は違えど、このような旨の内容を伝えられることがあると思います。

バージョンアップをやっている側としては、下手をしたら後戻りが大事になるかもしれません。
そのため、バージョンを上げるのを慎重になってしまいがちです。
実際に自分もそうでした。

しかし、バージョンアップはあくまで目的です。それだけやっていても、プロダクトに成長は起こりません。
バージョンアップを終えて、次の開発をする必要があります。

そのため、後戻りが発生するとしても、突き進まなければならない時があります。
そして、問題が発生したとしても、意外とあっさりとクリアすることができたりもします。

案ずるより産むが易し。
もちろん、ある程度の見通しを立てる必要はありますが、石橋を叩き過ぎて橋を渡らないのは本末転倒です。

心得2: チェックポイントとなるマイルストーンを設置しよう

バージョンアップ作業は、やってみないと実際にどれだけの対応が必要になるかはわかりません。
かといって、バージョンアップ作業を実施するには、ある程度の計画を立てて進める必要があります。
そして、その計画は計画立案自体の予測で立てるので、実際の作業状況とは乖離が出てしまう部分があるでしょう。

それ自体は、問題ではありません。
しかし、実態と違うからといって、元々立てた計画をそのままにしていては、作業者以外は何をしているのかわからなくなってしまいます。
そのため、計画の修正が必要かどうか、マイルストーンを設けて対応していくべきだったと反省しています。
とくに、gemのバージョンアップについては、あげたけど動かないので戻して、といった作業が何度も発生して時間を取られてしまう場合があります。
このような状態に陥った場合にも、スケジュール上でマイルストーンを設定しておけば、計画の立て直しなどのきっかけにもなります。

その時に有効な情報が「 」です。
分母がいくつ、現在の完了数がいくつ、試してエラーが出たのがいくつ、など。
その数字を基準として、今後の見通しを立てていけば良いでしょう。

心得3: 細かな進捗も共有しておこう

前述の「チェックポイントとなるマイルストーンを設置していない」とも関連するお話です。
バージョンアップ作業はその特性上、ひとりもしくは少人数で黙々と実施していく形態となります。
作業者が作業に没頭できる反面、作業管理者側から見た場合、その進捗状況は分かりにくいものとなります。
だから、管理側は不安にもなります。

もちろん、作業計画を立てて行うでしょうから、大きなポイントでは進捗状況を知ることができるでしょう。

言い換えれば、大きなポイントで進捗を確認するまでは、進捗遅れなどについて気付きづらい/対処しづらい、という内容でも状況に陥りやすいとも言えます。
railsのバージョンアップ作業では、gem同士のバージョンの組み合わせなどの対応で、想定よりも進捗が出ない場合もあったりします。
このような状態を担当者レベルで留めておくのは得策ではありません。

『現状このような状況にある、次アクションとしてこれをするが、うまくいかなかった場合には進め方/対応などを相談したい』

進捗が遅れそう/遅れてすぐの時に共有できれば、管理者側も他メンバーへの協力を要請できたり、さらに上位の管理者に対しても説明ができたりします。

ですので、バージョンアップ作業を行う際には、定期的にバージョンアップ状況を報告していきましょう。
デイリーで一方的に情報をあげるのでも良いかもしれません。

管理者によっては、時間を取られるために細かな進捗報告を嫌う方もいるかもしれません。
しかし、ここを疎かにすることで、より面倒な事態になる可能性もあります。
事前に、「slackなどで共有するような形で進めたいです」などと話をつけておくのもいいでしょう。

これ自体はバージョンアップ作業に特化した対応ではありません。
ですが、管理側もうまく巻き込むことで、より良い作業を実施していけるようになるという認識でいます。

心得4: Zeitwerk対応を疎かにしないように

今回のシステムでは、Railsのバージョンを5.2系から7系にバージョンアップしました。

そのため、Production環境下では、Zeitwerkの対応は必須となります。
しかし、このZeitwerk対応の確認を疎かにしてしまったことで、リリース作業は失敗。結果、リリース延期となりました。

事象の詳細としては、はじめてRails6系にバージョンアップした際に、Zeitwerkの対応を実施しました。
しかし、この時には、Zeitwerk対応をしたファイルを利用したgRPCサーバ処理が動作させられませんでした。
この時、他の修正内容も大量にあったこともあり、Zeitwerk対応を後回しにしてサービスを起動させることを優先しました。
ですが、その後Zeitwerk未対応のまま、バージョンアップ作業をどんどん先に進めてしまっていました。

しかも、残念なことに利用する「設定ファイル」の勘違いがありました。
弊社でもっとも利用されているRailsアプリでは、検証環境の設定ファイルは「config/environments/prestaging.rb」で用意されていました。
そして、今回のバージョンアップの対象のシステムにも同様に「config/environments/prestaging.rb」がありました。
このファイル上で、config.eager_load = trueに設定されていることを確認して、検証環境にアップ。
検証環境上で起動したことで、Zeitwerk対応は問題ない、と思い込んでしまっていました。

しかし、ステージング環境にアップした際には、あえなくデプロイ失敗。
CI設定を再確認すると、バージョンアップの対象のシステムでは「config/environments/prestaging.rb」は利用されていませんでした。
しかも、実際に検証環境で利用されていた設定は「config/environments/development.rb」でした。
つまり、config.eager_load = trueで確認していたと思った検証環境は、ずっとconfig.eager_load = falseで動いていたわけでした。

心得5: initializersのコード/動作を確認しておきましょう

今回のバージョンアップ対象となったシステムでも、「config/initializers/」配下にファイルがいくつか用意されていました。
その中には、環境によって処理が分岐されていたり、ちょっとだけ特定環境下でデータを参照するなどのコードが記されていました。
そして、initializersのコード/動作を確認しておかなかったせいで、バージョンアップ対応でコード修正した後にシステムの起動に失敗するなどの事象が発生しました。

具体的に事象として、以下のようなことを実施しました。

  • Zeitwerk対応でオートローダーで参照されるファイルのrequireされていた
    • Zeitwerkのオートロードは、initializerの実行の最後に実行されます。そのため、Zeitwerk対応でコード中からrequireを除去していたファイルを、initializersの処理中に参照してしまい、必要な参照がなくてロードエラーが発生してしまっていました。
  • rubyのバージョンアップによって廃止されたメソッドを利用していた
    • rubocopコマンドで廃止されたメソッドを確認していましたが、「config/initializers/」ファイル配下はrubocopの対象外指定であったため、気づくのに遅れた
  • コード上の設定で済んでいた内容がconfig設定に変更する必要があった
    • バージョンアップによってCarrierWaveとaws-fogのgemもバージョンが上がり、これまでコード上で対応できていた処理が無効となってconfigに設定を追加する必要がありました。しかし、設定の記述がinitializerにあったことによって、検証環境上で動作確認してはじめて設定が不足していたことに気づきました。

この辺りのコードは、機能を追加された後にはあまり触られることもないため、バージョンアップ作業ではじめて中身を見る/触るということも十分あります。
逆に言えば、必要になるまで意識されないということでもあります。
そして、意識されない場合は問題が起こってからの対処となります。

可能であれば、事前に確認しておき改修前/改修後のイメージができるようになっているのが望ましいと思いました。

心得6: 実機でのテスト方法を確認しておきましょう

バージョンアップしたシステムが正しく動作するかを確認すべく、検証環境上で行う動作確認リストを作成しておきました。
今回の対象になったシステムは、8割程度は画面上の操作で確認できるものでしたので、とくに苦労はありませんでした。

figure_5

しかし、残り2割については、以下のような起動条件が必要でした。

  • 呼び出し方法が特別(別のシステムから起動時にのみ実行される、など)
  • 前提条件で事前に特定のデータの状態にしておく必要がある
  • 外部サービスと接続するため特別な環境下で起動させる

そのため、それらの確認する際に、有識者に確認したり一定期間テストのために環境を借りたり、などを行いました。
これらの作業内容を事前に調査/確認しておかなかったことにより、テスト期間が2-3日延びてしまいました。

検証テストをする環境によっては、他の方が操作しているのを止めてもらうなどの対応も必要になってきます。

心得7: 本番稼働を想定した動作検証をしておこう

実際にどこまでできるかは開発環境/検証環境次第ではありますが、最終ゴールでは本番環境上で動作させることです。
そのためバージョンアップの作業実施時点で、開発環境/検証環境/本番環境(ステージング環境を含む)との違いを意識しておきましょう。
そして、本番で適用する設定を開発環境で確認しておきましょう。
加えて、もし必要であれば、ステージング環境のような本番環境相当での動作検証を実施しましょう。
これは、環境設定面でもそうですが、負荷的な意味でも確認できると望ましいです。

環境設定面で言えば、いざ本番リリースをしようとした際に「デプロイできない」という事象を防ぐきっかけになるはずです。
自分の失敗談で言えば、Development環境でしか動作させられない「Spring」がProduction環境でも起動するようにしてしまっていました。
結果、Production環境相当のステージング環境へのデプロイ失敗。

また、バージョンアップが完了してリリースされたとしても、問題が発生する場合があります。
実際、今回の対象システムではリリース後にメモリが断片的に上昇していく事象が定期的に発生しており、その対策が必要となってきています。

本番環境上での"データアクセス数"や"レスポンスとして返すデータ量"などの特性を知っておくのも、バージョンアップ後の調査労力を減らすための対策ともなるでしょう。

おわりに

以上、過去の自分に送りたい7つの心得を書かせていただきました。

失敗談ばかりで恥ずかしくもありますが、このような失敗談を挙げることで他のエンジニアの失敗回避に繋がってくれるのであれば、失敗も悪くはなかったのかなと思えそうです。

最後になりましたが、ココナラではエンジニアを募集しています。

もし興味をお持ちいただけましたら、カジュアル面談にお越しください。
https://open.talentio.com/r/1/c/coconala/pages/70417

また、募集求人については以下のリンクからご確認いただけます。
https://coconala.co.jp/recruit/engineer

Discussion