Mastodonをruby-3.1.0-preview1で走らせてみる
今日はハワイ時間(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.load
をYAML.unsafe_load
にしました。不穏だ。
(12月25日追記) プロと読み解く Ruby 3.1 NEWSによると、Psych.load
メソッドに、permitted_classes: [ロードを許可するクラスのリスト]
というキーワード引数を渡すのが正解なようです。
Default gemのbundled gem化への対応
プロダクション版で試験用のアカウントを作ろうとすると、het/pop
をrequire
できずにコマンドが異常終了しました。
$ 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