🐘

Mastodonをruby-3.1.0-preview1で走らせてみる

2021/12/20に公開

今日はハワイ時間(UTC-10)で2021年12月19日です。Rubyist近況[1] Advent Calendar 2021に憧れてあわててこの記事をまとめてみました。このところ毎年Rubyのリリース前に楽しませてもらっている作業についてまとめて、12月4日の隙間にもぐりこませてもらいます。

Mastodon

MastodonはRails 6で書かれたマイクロブログサービスで、他のMastodonサーバやActivityPubに準拠したサービスと連合してやりとりをすることができます。僕にとってうれしいのは、ぼっちで運用しているサーバでも連合先から常に自然な負荷を送ってもらえることです。ありがたい。今回はこの性質を活用して、自分の運用しているサーバ https://mastodon.zunda.ninja のRubyを3.0.3から3.1.0-preview1にしてみました。現状のコードを https://github.com/zunda/mastodon/tree/use-ruby31 で確認できます。

新しいRubyでもbundle installできるようにする

いくつかのgemでは、Gemspecで対応するRubyのバージョンをリリース版のものまでに限定しています。このようなgemについてはforkして対応するRubyのバージョンを広くしたものをpushして、MastodonのGemfileからfork版を参照するようにします。

Gemspec:

gem.required_ruby_version = [ ">= 2.6.0", "< 3.2.0" ]

Gemfile:

gem 'gem名', 'バージョン', git: 'https://github.com/zunda/gem名.git', branch: '私家版ブランチ'

Psychの更新への対応

Ruby 3.1.0-preview1ではスタンダードライブラリのPsychが更新されて、YAMLでエイリアスを解釈するのに、YAML.loadメソッドにaliases: true引数が必要になりました。Railsのconfig/database.ymlにエイリアスが使われているので、これを読んでいるらしいrails-settings-cached gemでaliases: true引数を追加しました。

でもこれ、Ruby 3.0.3ではエラーになっちゃうんですよね。どうすりゃいいんだ。

ArgumentError: unknown keyword: :aliases
/…/vendor/bundle/ruby/3.0.0/bundler/gems/rails-settings-cached-e3a3c992fac5/lib/rails-settings/default.rb:41:in `initialize'

(12月10日追記) この他、連合先からActivityPubで送られてくるアクティビティの処理にSidekiqから起動するタスクが実行中にPsych::DisallowedClass: Tried to load unspecified class: ActiveSupport::HashWithIndifferentAccessが落ちてしまいました。rails-settings-cached gemでYAML.loadYAML.unsafe_loadにしました。不穏だ。

(12月25日追記) プロと読み解く Ruby 3.1 NEWSによると、Psych.loadメソッドに、permitted_classes: [ロードを許可するクラスのリスト]というキーワード引数を渡すのが正解なようです。

Default gemのbundled gem化への対応

プロダクション版で試験用のアカウントを作ろうとすると、het/poprequireできずにコマンドが異常終了しました。

$ foreman run -e .env.production bin/tootctl accounts create zunda --email zundan@gmail.com --confirmed --role admin
  :
/…/vendor/bundle/ruby/3.1.0/bundler/gems/bootsnap-5fedb52badb3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:34:in `require': cannot load such file -- net/pop (LoadError)
	from /…/vendor/bundle/ruby/3.1.0/gems/zeitwerk-2.5.1/lib/zeitwerk/kernel.rb:35:in `require'
	from /…/vendor/bundle/ruby/3.1.0/gems/mail-2.7.1/lib/mail/network/retriever_methods/pop3.rb:36:in `<class:POP3>'
	from /…/vendor/bundle/ruby/3.1.0/gems/mail-2.7.1/lib/mail/network/retriever_methods/pop3.rb:35:in `<module:Mail>'
	from /…/vendor/bundle/ruby/3.1.0/gems/mail-2.7.1/lib/mail/network/retriever_methods/pop3.rb:4:in `<top (required)>'
	from /…/vendor/bundle/ruby/3.1.0/bundler/gems/bootsnap-5fedb52badb3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
  :

いろいろと試してみると、この環境で、ruby -e "require 'net/pop'"rbenv installによってRubyと一緒にインストールされたnet-pop gemを見つけるけれども、bundle exec ruby ruby -e "require 'net/pop'"LoadErrorになることがわかりました。ruby-3.1.0のリリースノートによるとnet-pop gemは3.1でdefault gemからbundled gemになったgemのひとつだそうです。bundle execでは、default gem以外はGemfileから明示的に依存する必要がありそうです。

Mail gemがnet-popなどbundled gemへの依存を明示するようにするプルリクエストはもう提出されています。古いバージョンのRubyとの互換性の確保が必要そうです。

JITの無効化

ここまでzunda/mastodonのuse-ruby31ブランチで作業しました。この時点で、MastodonのコードがそのままRuby 3.1.0-preview1で動くようになりました。すばらしい。

て、あれ?スワップをどんどん消費していきます。このサーバはこれまでRuby 3.0.3でRUBY_OPT--jit --jit-max-cache=100000にしてMJITしていましたが、3.1.0-preview1ではこのオプションを無くすことでスワップの消費を抑えることができました(下記のグラフで青がメモリの、緑がスワップの使用量です)。MJITの振る舞いが変化したのだろうと思います。

(12月25日追記) MJITを無効化してもruby-3.0に比べてメモリの利用量の増加を抑えられないようです。jelmallocを利用していたのをLinuxのデフォルトのmallocにしても同様。ScoutでRubyの内側から見ているメモリ利用量はあまり増えていないのですが、HerokuのDyno (512MBメモリ)で測定しているメモリ利用量は単調増加してしまっているようです。リリース版のruby-3.1.0ではどうなるでしょうか?

Pumaのdyno

Sidekiqのdyno

Discussion