🍊

RubyKaigi 2025 参加レポート

に公開

はじめに

こんにちは、 okumud です。

先日 RubyKaigi 2025 へ参加してきました。
弊社は Gold Sponsor として本年初協賛し、私を含めて  simomu, terandard, otsubo の 4人で参加しました。

https://prtimes.jp/main/html/rd/p/000000138.000085682.html

この記事ではそれぞれ印象に残ったセッションについて紹介します。

印象に残ったセッション

Make Parsers Compatible Using Automata Learning

writer: simomu

https://speakerdeck.com/makenowjust/make-parsers-compatible-using-automata-learning

CRuby のパーサの互換性をオートマトン学習を用いて検証するという内容の発表でした。

現時点の CRuby には parse.y によって生成されたパーサと、Prism の2つのパーサが存在していて、Ruby 3.4 ではデフォルトのパーサが Prism に変わっています。
Prism はほぼ完全に parse.y によるパーサと互換性をもつ、つまりは Ruby のコードをパースする振る舞いが従来と変わらないとのことですが、 これをどうやって保証するのかが問題だったとのことです。
現状はユニットテストを大量に用意したり、公開されている gem を大量にパースしたりとテストの物量で正当性を確認している状況で、これでもエッジケースを含めると両者が同一の振る舞いをするパーサであることを完全に保証できているものではなく、これをどうにかしたかったとのことです。

手法としては、パーサはオートマトンであることに注目し parse.y と Prism の両方のパーサのオートマトンを取得し、その XOR (片方のオートマトンが受理して、もう片方は受理しないもの) を計算し存在するかどうかを見ることで互換性のチェックを行うというものでした。
問題となるのはパーサのオートマトンをどうやって取得するのかという点で、これには L* というブラックボックスなシステムのオートマトンを学習するアルゴリズムを使ったとのことです。

↓実際に作成されたオートマトン学習を行う Ruby 製のツール

https://github.com/makenowjust/lernen

これを利用して実際に parse.y と Prism の互換性の差分を見つけることができ、実際に Prism 側のパッチを送ったとのことです。

https://github.com/ruby/prism/issues/3035

他の用途としては正規表現で書かれたコードを正規表現を使わない実装に変更したときに、振る舞いに変化が無いかを確認するのに使えるとのことでした。
また、このツールでは学習したオートマトンの状態遷移図を出力することもできるそうで、実際に振る舞いの異なるパーサのオートマトンの違いが一目で確認できる点も面白かったです。

parse.y と Prism の互換性をどうやって保証するのかは私も気になっていて、実際にテストを書く以外のアプローチがあると知りとても興味深かったです。

Performance Bugs and Low-level Ruby Observability APIs

writer: otsubo

https://docs.google.com/presentation/d/1lDnxFkc4URsi0LP4w1M5IXv5A02AG_HUy3gW_SpEWOA/present?slide=id.g34edb417dc3_0_1471

Ruby のコードからは直接アクセスできない詳細なプロファイルを取得する方法についての発表でした。発表者は Datadog Ruby profiler 機能 の開発者である @KnuX 氏です。

※弊社でもこの機能には大変お世話になっています。
https://zenn.dev/socialplus/articles/continuous_profiler

CRuby VM が隠す詳細な内部状態は Ruby からは直接アクセスできませんが、CRuby VM が提供する低レベル C API をラップすることで詳細な情報にもアクセスできるという内容でした。具体例として:

  • TracePoint APIs による GC イベント等の観測
  • Frame-profiling APIs / Debug inspector APIs による backtrace の取得
  • GVL Instrumentation API による GVL の待ち時間計測

などが紹介されました。

最後に、上記の低レベル API を組み合わせることでオリジナルのプロファイラを自作できるというデモがありました。GVL の挙動の可視化がわずか100行程度のコードで実現できており「これなら自分もできるかも」という気持ちにさせてもらえたのが大きな収穫でした。

コード例は lowlevel-toolkit リポジトリにアップロードされています。
https://github.com/ivoanjo/lowlevel-toolkit

Making TCPSocket.new "Happy"!

writer: okumud

https://speakerdeck.com/coe401_/making-tcpsocket-dot-new-happy

IPv6 で接続できなかったり遅かった場合に、IPv4 へのフォールバックが遅くなるため、名前解決を IPv4 と IPv6 の両方で同時に開始する Happy Eyeballs Version 2(HEv2)に対応し、接続を高速化したそうです。
Socket.tcp の対応は昨年の RubyKaigi で発表がありました。 TCPSocket.new への対応も同じように行う予定だったそうですが、Socket.tcp の再修正が必要だったとのことでした。Ruby 3.4 リリース直前には緊迫したやりとりもあったようです。

ソケットの入力待ち等で select を使っていて、FD(File descriptor) の最大値問題でエラーとなる問題が報告されたそうです。man page では select(2) ではなく、 epoll(2) や poll(2) を使うように案内されていて似た目的で使える機能ですが、インターフェースや動作特性が大きく異なるため、互換性のある方法として rb_thread_fd_select を採用するに至った、という話がありました。 select(2) は使ったことがあるので、印象に残りました。

弊社サービスで外部へリクエストを行う場合は Faraday を使うことがあります。Faraday はその中で(Net::HTTPアダプタの場合) TCPSocket を使用しているため、このパフォーマンス改善がサービスの応答性能向上に役立つと思いました。

なお、動きが変わるため既存の振る舞いに戻すオプションも提供されているそうです。

Ruby 全体で無効にしたい場合は環境変数として RUBY_TCP_NO_FAST_FALLBACK=1 を設定するか、Socket.tcp_fast_fallback=false を Ruby プログラムの中で呼び出してください。またはメソッド単位で無効化する場合、TCPSocket.new (TCPSocket.open) と Socket.tcp のキーワード引数として fast_fallback: false を利用してください。

On-the-fly Suggestions of Rewriting Method Deprecations

writer: terandard

https://speakerdeck.com/ohbarye/on-the-fly-suggestions-of-rewriting-method-deprecations

Deprecation warning はライブラリ開発者とユーザの両方の対応が必要ですが、それをより管理しやすい方法を提案されていました。

一言で言うと:

ライブラリ開発者が非推奨コードの移行先を定義しておくと、ユーザがそのコードを実行したら移行先のコードに自動で修正してくれるものを作った

以下は発表内容についての紹介です。

従来の Deprecation warning は以下のような方法でユーザに提示されることが多いです:

  1. ドキュメント(README など)に出す
  2. 実行時に警告を出す
  3. 静的解析時に警告を出す(Rubocop, deprecation_toolkit など)

しかし上記の方法では時間がかかるし、手動で修正するので大規模なコードベースでは抜け漏れが発生する可能性もあります。

他のプログラミング言語ではどのように対応しているのか調査したところ、Pharo という言語での仕組みが参考なったそうです。この Pharo という言語は初めて知りました。

https://pharo.org/

この仕組みが冒頭で記載した「ライブラリ開発者が非推奨コードの移行先を定義しておくと、エンドユーザがそのコードを実行したら移行先のコードに自動で修正してくれる」というものです。

今回作成されたのが Deprewriter gem です:

https://github.com/ohbarye/deprewriter-ruby

ライブラリ開発者は以下のように変換先のメソッドを定義することで、 old_method が実行された場合 new_method に変換されるようになります:

extend Deprewriter
deprewrite :old_method,
  from: '.call_node[name=old_method]', # Rewrite calls only when matched
  to: 'new_method({{arguments}})'

from オプションを使用すると、変換対象のパターンを指定することができます。
e.g. 引数が位置引数の時は変換したいが、キーワード引数の時は無視する

Q: なぜ静的解析ではなく実行時に修正するのか?
A: Ruby のような動的型付け言語で静的解析した際、同名の別メソッドがある、オーバーライドされている、などの場合に修正の過不足が発生するため

Q: 実行時にコードを書き換えるのは安全じゃないのでは?
A: 基本的に本番環境で試さず、開発環境やテスト環境で実行することを想定している
また複数のモードを用意しているので、必要に応じて切り替えることができる:

  • 警告をログに出すだけ
  • patch file を作成する
  • 書き換える

発表を聞いた感想としては、導入できるとユーザ目線として Deprecation Warning の対応が自動化されるので楽になりそうで良いなと思いました。ただ一方で実行されないと修正できないため、異常系で使用するようなメソッドやテストカバレッチが低い場合には漏れが出てきそうだなとも思いました。

また内部実装の話が一部ついていけなかったので、ソースコードやスライドを見返して理解を深めたいと思います。

所感

日頃業務で利用している Ruby の内部を知ることができたことや、互換性の維持などのコミッターの考えを知れて、大変有意義でした。
スライドも公開されているため、振り返ることで理解を深めていきたいです。得られた知見をチームやプロダクトに活かしていきたいです。

Social PLUS Tech Blog

Discussion