こんにちは。2022年12月入社の石田です。STARTUP DBの主にバックエンドのエンジニアをしています。今回はSTARTUP DBのバックエンドのRubyのバージョンアップをした話をしたいと思います。
2023/03/31をもってRuby 2.7系のサポートが終了しました。当プロジェクトでは Ruby 2.7.1を使っていたため、急遽Rubyのアップデート作業が必要に。どうせアップデートが必要ならということで、可能な限り最新のバージョンまでアップデートすることになりました。
アップデートは以下のように段階を踏んで行うことにしました。
2.7.1 → 2.7.8 → 3.0.6 → 3.1.4 → 3.2.2
以下は、Rubyのアップデートをしていく中で遭遇したエラーとその対応の記録です。
Ruby 2.7.1 → Ruby 2.7.8
何事もなくアップデート完了。 動作も問題なし。
Ruby 2.7.8 → Ruby 3.0.6
URI.escapeが削除された影響
当プロジェクトでは未だにpaperclipを使っています。このpaperclipがエラーを出しました。 Ruby 3.0でURI.escapeが削除されたのですが、paperclipではまだ使われているようです。 このgemはすでにメンテナンスされていないため、他のgemに移行が必要なんですが、工数が必要なため今回は諦めてモンキーパッチを当てて対応することにしました。 以下、モンキーパッチです。こちらからお借りしました。
config/initializers/uri.rb
# frozen_string_literal: true require 'uri' # # Ruby 3.0.0でURI.escapeメソッドが削除された。 # paperclipがこれに対応していないため、他のgemに移行するまでモンキーパッチを当てる。 # https://stackoverflow.com/questions/68174351/undefined-method-escape-for-urimodule # module URI class << self def escape(str) alpha = 'a-zA-Z' alnum = "#{alpha}\\d" unreserved = "\\-_.!~*'()#{alnum}" reserved = ';/?:@&=+$,\\[\\]' unsafe = Regexp.new("[^#{unreserved}#{reserved}]") str.gsub(unsafe) do us = Regexp.last_match(0) tmp = '' us.each_byte do |uc| tmp += format('%%%02X', uc) end tmp end.force_encoding(Encoding::US_ASCII) end end end
rss gemの追加
Ruby 3.0から rssライブラリがbundled gemsになりました。これは素直にGemfileに追加することにしました。
Gemfile
gem 'rss'
elasticsearch gemのアップデート
elasticsearchが以下のようなエラーを出しました。
wrong number of arguments (given 1, expected 0) /usr/local/bundle/gems/activerecord-6.1.6.1/lib/active_record/relation/batches.rb:128:in `find_in_batches’ /usr/local/bundle/gems/elasticsearch-model-5.1.0/lib/elasticsearch/model/adapters/active_record.rb:105:in `__find_in_batches’ /usr/local/bundle/gems/elasticsearch-model-5.1.0/lib/elasticsearch/model/importing.rb:122:in `import’
これはRuby 3.0の位置引数とキーワード引数の変更の影響のようです。 こちらのコミットで修正されていますので、このコミットが入ったバージョンまでelasticsearchをバージョンアップしました。
elasticsearch gemのダウングレード
先程、elasticsearch gemを最新までアップデートしたのですが、これが良くなかったようです。ローカルでは動作するのですが、STG環境で以下のエラーが発生しました。
Elasticsearch::UnsupportedProductError: The client noticed that the server is not a supported distribution of Elasticsearch.
当プロジェクトではSTG環境及び本番環境はAWSを使っておりElasticsearchもAWSが提供するものを使っています。
AWSはあるバージョンからElasticsearchの呼称をやめ、OpenSearchと呼び改めました。またElasticsearchはクライアントライブラリに「接続先がElasticsearchじゃないとエラーになる」修正を加えました。この変更が加えられたのがelasticsearch gemのバージョンでは7.14系になります。なのでelasticsearch gemのバージョンが7.14以上のものを使ってOpenSearchに接続しようとするとエラーが発生したんですね。これについては以下の文献などが詳しかったです。
- https://aws.amazon.com/jp/what-is/opensearch/
- https://stackoverflow.com/questions/68762774/elasticsearchunsupportedproducterror-the-client-noticed-that-the-server-is-no
- https://zenn.dev/hajimeni/articles/682e81fa68c7af
解決方法は文献のとおりにelasticsearch gemのバージョンを14より下のバージョンを使うことです。
以下のように対応しました。
Gemfile
gem 'elasticsearch', '7.13.3' gem 'elasticsearch-model', '7.2.1' gem 'elasticsearch-rails', '7.2.1'
fixture_file_uploadのDEPRECATION WARNING
以下のようなWarningが出るようになりました。
DEPRECATION WARNING: Passing a path to `fixture_file_upload` relative to `fixture_path` is deprecated.
これは以下のような単純な置換で対応できます。
# before fixture_file_upload('150x150.png', 'image/png') # after Rack::Test::UploadedFile.new('spec/fixtures/150x150.png', 'image/png')
Ruby 3.0.6 → Ruby 3.1.4
bundled gemsの追加
Ruby 3.1から以下のgemがbundled gemになりました。bundlerで使う場合はGemfileに明示的に指定する必要があります。
当プロジェクトでは上記のうちnet-ftpとprime以外は使っていたのでGemfileに追加しました。
psych 3系の追加
db:migrate時に以下のエラーが発生しました。
Psych::BadAlias: Unknown alias: default
これはpsychというYAML解釈用のGemの4系と3系で解釈が変わっているのが原因です。psychのバージョンを3系に固定してあげれば解消します。
Gemfile
gem 'psych', '~> 3.1'
feedjira gemのアップデート
当プロジェクトではrssでデータを収集するためにfeedjiraというgemを使っています。もともとこのgemのバージョンが2.1.4のものを使っていたのですが、Ruby 3.1に対応していなかったので最新の3.2.2に上げました。それに伴って使い方も変わったのでコード修正を行います。
# 古い書き方 feed = Feedjira::Feed.fetch_and_parse(url) # 新しい書き方 xml = HTTParty.get(url).body feed = Feedjira.parse(xml)
ここで問題が発生しました。一部のurlでHTTParty.get(url)
をしたときに403エラーが返ってきてしまうのです。ブラウザで開いてもcurlでたたいても正常なのになぜ?調査してもちゃんとした原因はわかりませんでしたが、以下のようにheaderをつけると回避できることがわかりました。
HTTParty.get(url, headers: { 'User-Agent' => 'Chrome/58.0.3029.110' }).body
bundlerのupdate
以下のようなWarningが出るようになりました。
Calling `DidYouMean::SPELL_CHECKERS.merge!(error_name => spell_checker)' has been deprecated. Please call `DidYouMean.correct_error(error_name, spell_checker)' instead.
これはbundlerを最新バージョンにアップデートすれば出なくなります。
bundlerのアップデート方法は以下です。
bundle update --bundler
Dockerを使っている場合などでbundlerのバージョンを固定したい場合は以下のようにします。
Dockerfile
ENV BUNDLER_VERSION=2.4.12 RUN gem install bundler --no-document -v 2.4.12
config.action_mailer.delivery_jobの設定
メーラーが動作しないバグに遭遇しました。メソッドの引数の数が正しくない旨のエラーだったのですが、ソースコードを見た限りでは間違いは無さそうでした。結果としては同時に出ていた以下のWarningに対応したら正しく動作するようになりました。
DEPRECATION WARNING: Sending mail with DeliveryJob and Parameterized::DeliveryJob is deprecated and will be removed in Rails 6.2. Please use MailDeliveryJob instead. (called from block in execute at /usr/local/lib/Ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/task.rb:281)
対応方法は以下の通りです。
config/application.rb
config.action_mailer.delivery_job = 'ActionMailer::MailDeliveryJob'
ただ、この修正の方法が良かったのかは正直わかりません。とりあえず動くようにはなりましたが、相変わらず使っているのはMailDeliveryJobではなくDeliveryJobなので、ここは別途対応が必要だと思います。
Ruby 3.1.4 → Ruby 3.2.3
unicornの不具合
Rubyを3.2.3にあげて、いくつかのgemをバージョンアップしたところCIも通り、ローカルでは問題なく動作するようになりました。そこでSTG環境で動作確認したのですが、問題が発生しました。一部APIが正常に動作していないようなのです。しかもrailsのログにはエラーなどは出力されていません。色々原因を探ったのですが結果としてはunicornが原因でした。railsにはエラーが出ていませんでしたが、unicornはエラーを出していたのです。
unicorn_error.log
E, [2023-04-13T17:42:39.079818 #29845] ERROR -- : app error: undefined method `=~' for 5:Integer (NoMethodError)
unicornはもうメンテナンスされていないgemです。Ruby 3.2には対応していません。そのためunicornをpumaなどに置き換えない限りRuby 3.2へのアップデートは不可能です。今回はRuby 3.2へのアップデートは諦めることにしました。
2023/07/06 追記
「unicornはもうメンテナンスされていないgemです。」と書きましたが。現在も開発が続いているようです。 以下のリポジトリは開発中のリポジトリがGitHub上にミラーリングされたものになります(これはrubygems.orgのページからはたどり着けないので注意)。
GitHub - defunkt/unicorn: Unofficial Unicorn Mirror.
このことは中村 涼(r7kamura)さんから教えてもらいました。 この場を借りてお礼申し上げます。誠にありがとうございます。
また、unicorn_error.logの内容について、レスポンスヘッダーの値に本来利用すべきではない5
などの値が入っていないかどうか確認することも助言頂きました。
重ねてお礼申し上げます。
ただ、上記GitHubのリポジトリの最新が本番運用できるものなのかどうか私では判断できませんでした(バージョンタグなどもついていない)。 そのため、件のGitHubのリポジトリの最新unicornを使うようにするのか、pumaなどに置き換えるのかはチーム検討中です。
追記終わり。
終わりに
Ruby 3.2まではアップデートできませんでしたが、もともとの目的であるメンテナンスされなくなった2.7系から脱却することは達成しました。
ここまで3週間とちょっとかかりました。
ここにたどり着くまでには、ここでは書ききれなかった困難がたくさんありました。一部を箇条書きで概要だけ残しておきます。
- アップデート作業中にRubyのパッチバージョンのアップデートがあった
- bundlerのアップデート作業が終わった直後にbundlerのアップデートがあった
- bundlerの再アップデート作業が終わった直後(翌日)にまたbundlerのアップデートがあった
- Ruby のバージョンを上げた場合はunicornをUSR2ではなくQUITで終了して立ち上げ直さなければならない仕様
- Ruby 3.1をSTG完了にデプロイしたところMySQLで謎のLost connectionやmalformed packetが発生。しかもRuby 2.7に戻しても直らなくなった。(これはRESET QUERY CACHEを実行することで解消することがわかりました。)
- feedjiraのエラー対応は、さも事前に対応した風に書いているが、実は本番リリース後に発覚&対応した。
数々の困難がありましたが無事にアップデートが完了できたのはチームの皆さんのおかげです。私はひたすらバグを潰していただけですが、その裏では
- rspecで拾いきれないバグのためにQA表の作成と実施
- ダウンタイムが発生しないリリース手順の作成と実行
- その他トラブル対応
などの作業がありました。これは私1人では到底できないことです。チームの皆さんにはこの場を借りて感謝申し上げます。
採用
当社ではエンジニアを募集中です。ぜひ一緒に成長していきませんか。ご興味ありましたらぜひ一度カジュアルにお話できたらと思います。
採用ページはこちら。