for Startups Tech blog

このブログのデザインを刷新しました。(2023/12/26)

Rubyを2.7.1から3.1.4にアップデートした話と3.2.2を諦めたわけ

こんにちは。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に接続しようとするとエラーが発生したんですね。これについては以下の文献などが詳しかったです。

解決方法は文献のとおりに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 0.1.3
  • net-imap 0.2.2
  • net-pop 0.1.1
  • net-smtp 0.3.1
  • matrix 0.4.2
  • prime 0.1.2
  • debug 1.4.0

当プロジェクトでは上記のうち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人では到底できないことです。チームの皆さんにはこの場を借りて感謝申し上げます。

採用

当社ではエンジニアを募集中です。ぜひ一緒に成長していきませんか。ご興味ありましたらぜひ一度カジュアルにお話できたらと思います。

採用ページはこちら