こんにちは、DevRelブロックのikkouです。2024年5月15日から17日の3日間にわたり沖縄県は那覇市で「RubyKaigi 2024」が開催されました。ZOZOは例年同様プラチナスポンサーとして協賛し、スポンサーブースを出展しました。
- ZOZOとWEARとRubyKaigi
- エンジニアによるセッション紹介
- Generating a custom SDK for your web service or Rails API
- Namespace, What and Why
- YJIT Makes Rails 1.7x Faster
- Using Ruby in the browser is wonderful.
- An adventure of Happy Eyeballs
- Embedding it into Ruby code
- Unlocking Potential of Property Based Testing with Ractor
- Let's use LLMs from Ruby 〜 Refine RBS types using LLM 〜
- The depths of profiling Ruby
- It’s about time to pack Ruby and Ruby scripts in one binary
- スポンサーブースの紹介
- After RubyKaigi 2024〜メドピア、ZOZO、Findy〜を開催します!
- おわりに
ZOZOとWEARとRubyKaigi
ZOZOとRubyKaigiの関係は前身にあたるVASILY時代のRubyKaigi 2017から始まっています。翌年のRubyKaigi 2018ではスタートトゥデイテクノロジーズとして初めて協賛、その翌年のRubyKaigi 2019ではZOZOテクノロジーズとしてRubyスポンサーに協賛、現CTOの瀬尾がスピーカーとして登壇しています。このときはファッションチェックアプリとRuby on Lambdaで開発したランキングサイトを展示していました。
その後、コロナ禍を経て再開したRubyKaigi 2022からはファッションコーディネートアプリ「WEAR」のバックエンド開発を担うチームが中心となって協賛とスポンサーブースの出展を続けています。
- RubyKaigi2017参加レポート(全日分)とスライドまとめ
- RubyKaigi2018参加レポート
- RubyKaigi 2019参加レポート〜sonots登壇セッション & エンジニア8名による厳選セッション
- RubyKaigi 2022参加レポート 〜エンジニアによるセッション紹介〜
- RubyKaigi 2023参加レポート 〜エンジニアによるセッション紹介〜
私たちが運営しているファッションコーディネートアプリ「WEAR」のバックエンドはRuby on Railsで開発しています。2013年にVBScriptで作られたシステムですが、2020年頃からVBScriptのシステムをコードフリーズし、Rubyへのリプレイスをはじめました。現在もリプレイスを進めながら、新規の機能もRubyで開発しています。
また、かねてよりMatzさんを技術顧問としてお迎えし、月次でMatz MTGと題したオンラインミーティングを実施しています。Rubyど真ん中の話から広く技術的に興味がそそられる話まで色々話せる時間として人気です。
昨年10月には10周年を迎えたWEARですが、RubyKaigi 2024の会期直前となる5月9日に「WEAR by ZOZO」としてリニューアルしました。リニューアル直後となるスポンサーブースの出展だったこともあり、会期中に不測の事態が発生しないか心配していましたが、何事もなく、ブースを訪れた方に実機で新機能を試してもらえました。
そんなZOZOとWEARとRubyKaigiの関係をお伝えしたところで、RubyKaigi 2024に参加したエンジニアがピックアップしたセッションの紹介と、スポンサーブース紹介の2軸でお送りします。ボリュームたっぷりの記事となっています!
エンジニアによるセッション紹介
Generating a custom SDK for your web service or Rails API
@tsuwatchです!@mullermpさんの「Generating a custom SDK for your web service or Rails API」が個人的にかなり良かったのでご紹介します。
本セッションはAWSで実際に使用しているSmithyというIDLと実際にsmithy-rubyを使いながら何ができるのかを紹介するものでした。aws-sdk-rubyに実際に使われているそうです。
Smithyとは、プロトコルに依存しないIDLで、クライアント、サーバ、ドキュメントなどを生成するためのツールセットです。AWSが提供している数万のサービスに対してSDKを生成してきた叡智が詰まっています。
一部抜粋ですが、以下のようにサービスや、リソース、リソースに対する操作についての定義を記述すると、それを実行できるSDKが自動生成されるというものです。
$version: "1.0" namespace example.railsjson use smithy.ruby.protocols#railsJson use smithy.ruby.protocols#UnprocessableEntityError /// Rails High Score example from their generator docs @railsJson @title("High Score Sample Rails Service") service HighScoreService { version: "2021-02-15", resources: [HighScore], } /// Rails default scaffold operations resource HighScore { identifiers: { id: String }, read: GetHighScore } /// Modeled attributes for a High Score structure HighScoreAttributes { /// The high score id id: String, /// The game for the high score game: String, /// The high score for the game score: Integer, // The time the high score was created at createdAt: Timestamp, // The time the high score was updated at updatedAt: Timestamp } /// Permitted params for a High Score structure HighScoreParams { /// The game for the high score @length(min: 2) game: String, /// The high score for the game score: Integer } /// Get a high score @http(method: "GET", uri: "/high_scores/{id}") @readonly operation GetHighScore { input: GetHighScoreInput, output: GetHighScoreOutput } /// Input structure for GetHighScore structure GetHighScoreInput { /// The high score id @required @httpLabel id: String } /// Output structure for GetHighScore structure GetHighScoreOutput { /// The high score attributes @httpPayload highScore: HighScoreAttributes }
そして以下のような呼び出しが可能になります。
require 'high_score_service' client = HighScoreService::Client.new(endpoint: 'http://127.0.0.1:3000') c.get_high_score(id: ‘1’)
OpenAPIではダメなのかという意見もあるかと思いますが、Smithyはプロトコルに依存しないというところが明確に差分としてあります。そのため、RESTful APIはもちろん、RPC、pub/subなどを定義できます。しかも、なんとSmithyはOpenAPI、JSON Schema、Protobufに変換できます。
https://github.com/disneystreaming/smithy-translate
その他にもかなりの拡張性があり、Paginators, Waiters, Interceptorsなどさまざまな拡張性があります。
AWSほど巨大なサービスを社内もしくは社外に提供している事例はそれほどないでしょうが、OpenAPIよりもプログラマブルで拡張性があり、柔軟に使用できるので検討の余地が大いにあるのではないでしょうか。場合によっては自分たち開発者側だけの利用だけでも役に立つかもしれません。
セッションを聞いていると、自動生成されるデモを実演するたびにcoolと気持ちよさそうにしていたので、その気持ちがすごくわかりますと思いながら聞いていました。
- その他の参考資料
Namespace, What and Why
伊藤です。RubyKaigiには今年で2回目の参加でしたが、今年も内容が幅広く、興味深いセッションばかりでした!
私からは@tagomorisさんの「Namespace, What and Why」をご紹介します。
このセッションでは、Namespaceの概要や目的、デモ、そして将来の展望までをお話しされており、非常に興味深い内容でした。
Rubyにはグローバルな名前空間しか存在しません。Namespaceは、Rubyに仮想的なトップレベル名前空間を導入し、ライブラリなどをグローバル名前空間から独立した形でrequire/loadできるようにする仕組みです。
Namespaceの導入の目的は、ライブラリなどの名前、定義、バージョンの衝突を回避することです。
- 名前の衝突
- 通常、Rubyでは階層化された名前を使う(
Foo::Bar::Baz
) - 既に他の誰かにFooを使われていたら、新たにFooを複製できない
- 通常、Rubyでは階層化された名前を使う(
- 定義の衝突
- Module/Classの定義を変えると、全アプリケーションに影響が出る(定数やグローバル変数などをアプリケーション毎に変えられない)
- バージョンの衝突
- アプリケーションによってライブラリなどの依存バージョンが異なる(ライブラリをアップデートすると、意図しない箇所に影響を及ぼす可能性がある)
Namespace on readでは、Namespace毎にrequire/loadするライブラリなどを明示的に指定し、それぞれ独立した形で読み込むことを実現しています。裏側でNamespace毎にライブラリなどを複製し、トップレベル名前空間を仮想的に分けることで実現しているようです。
RubyのPlaygroundでNamespace on readを試せたので、デモを紹介します!
例えば、以下の2つのモジュールがあったとします。
#--- ./module1.rb module Hoge def self.foo puts 'module1' end end
#--- ./module2.rb module Hoge def self.foo puts 'module2' end end
上記2つのモジュールは、モジュール名が衝突しているため、両方のモジュールを使おうとすると、以下のようにどちらか一方に上書きされてしまい、同時には使用できません。
require('./module1.rb') require('./module2.rb') Hoge.foo #=> module2
ところが2つのNamespaceを用意し、それぞれにどのモジュールを読み込むかを明示的に指定することにより、トップレベル名前空間を仮想的に分けられ、両方のモジュールを使用できるようになります。
namespace1 = Namespace.new namespace1.require('./module1.rb') namespace2 = Namespace.new namespace2.require('./module2.rb') namespace1::Hoge.foo #=> module1 namespace2::Hoge.foo #=> module2
Namespaceを用いると、namespace1とnamespace2の2つにトップレベル名前空間を分離させ、見事に名前空間の衝突を回避できていますね!
将来的に、例えばBundlerにNamespaceが組み込まれたら、gemのバージョンの衝突が自動的に回避されるようになるかもしれません。つまり、ライブラリの依存関係の地獄から抜け出せるということです!
Namespaceはまだ絶賛開発中とのことなので、リリースを心待ちにしております!
YJIT Makes Rails 1.7x Faster
小山です。私からは@k0kubunさんの「YJIT Makes Rails 1.7x faster」をご紹介します。
Ruby 3.3 YJITの高速化に寄与した対応の詳細とRuby 3.4 YJITによってRailsアプリケーションが1.8倍高速化されたということが主題のセッションでした。タイトルは1.7xとなっていますが、発表当日までの間に1.8倍に高速化されたとのことで、冒頭で訂正されていました。
YJITはCRubyのJITコンパイラで、30回以上呼び出されるメソッドの高速化が行われます。
Shopifyで最もトラフィックが多いShopify StoreFrontで、Ruby 3.3 YJITとインタプリタを比較した場合、Ruby 3.3では平均17%高速化されたとのことです。
YJITはデフォルトではオフになっているので有効化するには以下のいずれかの設定が必要です。
- コマンドライン引数
--yjit
で有効化 - 環境変数
RUBY_YJIT_ENABLE=1
で有効化 - Rubyコード内の
RubyVM::YJIT.enable
で有効化
Rails 7.2でRuby 3.3以降を使用している場合、デフォルトのイニシャライザ内でRubyVM::YJIT.enable
が呼び出され有効化されるようになるとのことで、YJITが標準となります。
YJITのメモリ使用率を最小化するには以下の方法があります。
--yjit-exec-mem-size
で生成するコード量をコントロールする- Ruby 3.31でのデフォルトは48MiB
--yjit-exec-mem-size
の3〜4倍のメモリを使用する- 2〜3倍分はメタデータに対して使われる
Ruby 3.3のYJIT最適化で最もインパクトのあった以下の対応によって、Railsbenchが7%高速化されたとのことです。
- サポートされていない呼び出し方や分岐の数の多い呼出しでのインタプリタへのフォールバックが行なわれなくした
- 例外ハンドラのコンパイルを行った
- スタック操作でレジスタが使われるようにした
- Ruby 3.4ではスタック値のようなローカル変数が最適化されて、
Kernel#binding
が呼び出されないと推測している
- Ruby 3.4ではスタック値のようなローカル変数が最適化されて、
- 数値, true, false, nil., :symbolなど単純な値を返すメソッドのインライン化
- Ruby 3.4ではself, local variablesを返すメソッドにも対応
- 多くのCメソッドのインライン化
- Ruby 3.4では
jit_prepare_lazy_frame_call
によって遅延フレーム呼び出しが可能になる
- Ruby 3.4では
YJITではプロダクションのアプリケーションで10〜20%ほど高速化されるためYJITを有効化しましょう。より多くのコードがJITコンパイルされ、レジスタ割り当てがされ、メソッドがインライン化されるためRuby 3.3にアップグレードしましょう。と締められていました。
RubyバージョンのアップグレードおよびYJIT有効化による恩恵がとても大きいことを話されており、私たちのアプリケーションにも早く導入したいと思いました!
Using Ruby in the browser is wonderful.
chikaです。私からは@ledsunさんの「Using Ruby in the browser is wonderful.」をご紹介します。
このセッションは、ruby.wasm
を使用する上で不足している機能を実装し、ruby.wasm
に追加したということと、ruby.wasm
を使用したRuby製のフロントエンドフレームワークの紹介でした。
JavaScriptではできるがRuby(ruby.wasm
)ではできないこととして、以下があります。
- new演算子
- new演算子は、JavaScriptでは
new.Foo()
と記述する演算子ですが、RubyではFoo.new
というメソッド呼び出しとなる
- new演算子は、JavaScriptでは
- プロパティ呼び出し
- JavaScriptにおけるプロパティ呼び出しも同様に、Rubyではメソッド呼び出しとなる
- 外部リソースにアクセスする
- JavaScriptではローカルファイルへのアクセスや、OSの機能を呼び出せるが、Rubyではできない
ruby.wasm
がCRubyとJavaScriptの架け橋となり、JS.global
やJS.eval
、JS::Object
によってJavaScriptのAPIを呼び出すことで可能となっているが、JSとRubyのコードが入り混じってしまい、分かりにくくなってしまう
現状のRubyやruby.wasm
できない部分や分かりにくいところを改善するために、以下2つの改善をruby.wasm
本体に向けてPRを作成したとのことでした。
1つ目は、JavaScriptのオブジェクトを、newメソッドから作れる修正になります。
元のruby.wasm
ではnew演算子が存在しないため、JS.eval
によってJavaScriptのnew演算子を呼び出す必要がありました。
JS.eval 'return new URLSearchParams(location.search)'
それが、今回のPRによって、以下のようにnewメソッドにて記述することが可能となります。
JS.global[:URLSearchParams].new(JS.global[:location][:search])
実際のPRはこちら:https://github.com/ruby/ruby.wasm/pull/246
2つ目は、JavaScriptのJS::RequireRemote
をrequire_relative#load
に互換する修正です。
機能としては、Rubyのrequire_relative
と互換性があり、
- 拡張子なしの機能名で指定可能
- 相対パスでの解決
- 二重ローディングの防止
があります。
言語本体のファイルに何も手を入れる必要がなく、ブラウザとターミナルでcompartibleになっているそうです。
この修正によって、JavaScriptでの以下の構文が、
<script type="text/ruby" src="lib_a"></script> <script type="text/ruby" src="lib_b"></script> <script type="text/ruby" src="main"></script>
ruby.wasm
にて以下のように記述することが可能となります。
require_relative 'lib_a' require_relative 'lib_b'
実際のPRはこちら:https://github.com/ruby/ruby.wasm/pull/292
次に作成しているものとして、Ruby製のフロントエンドフレームワークを紹介していました。
Ruby製のフロントエンドフレームワークはおそらく現在1つもなく、史上初のフレームワークかもしれないとのことで、とても面白い取り組みでした。
現在、ruby.wasm
を使ったRuby製のフロントエンドフレームワーク「Orbital Ring」というものを作成していて、現在の機能は以下になります。
- AutoLoader
- Railsのclassic autoloaderを参考に、require_relativeが必要なくなるよう実装
- Rendering
- ブラウザで動作するので、HTMLをレンダリングして書き換えたいため、画面を描写するメソッドを追加し、erbに渡すよう実装
- Event Binding
- Railsのroutingのようなものを実装
現在120行ほどのコード量とのことでした(少ない!)
また、実際にフレームワークを使用して作成したアプリケーションを公開していて、実際に以下のリンクからアクセスして動かすことが可能となっていました。
https://ledsun.github.io/kakikata/
セッションの紹介は以上になります。
ruby.wasm
が登場してから2年ほど経ちましたが、毎年ruby.wasm
に関するセッションがいくつもあり、どんどん進化していっているなという印象で、追うのが楽しくなっています! 来年もまたどう進化するのか楽しみです。
An adventure of Happy Eyeballs
春日です。私からは@coe401_さんの「An adventure of Happy Eyeballs」をご紹介します。
このセッションでは、Happy Eyeballs Version 2というRFC 8305で定義された接続アルゴリズムを用いてsocketライブラリを改修した際の考慮事項や実装方法の説明、実際にデモを行い従来の課題を解決できたことをお話しされていました。
従来のRubyのsocketライブラリの実装としては、次のようになっています。
- DNSサーバに対し、接続したいドメイン名を問い合わせ、対象のIPアドレスを取得
- 解決されたIPアドレスに対し、接続を試行
- 接続が確立したらsocketオブジェクトを返す
しかし、従来の実装方法では次のような課題がありました。
- DNSサーバへの対象サーバのIPv4とIPv6の問い合わせは順番に行われるため、先に問い合わせた方の名前解決に時間がかかる場合、後に問い合わせる方がすぐにIPアドレスを返せるとしても、先に問い合わせた方の回答を待機する必要がある
- IPv4, IPv6の名前解決が両方とも成功した際、それぞれへの接続も順番に行われるため、先に接続試行した方が長時間接続の確立ができない場合、次に試行するアドレスがすぐに接続可能だとしても、先に接続試行した方が失敗するまで次の接続を待機する必要がある
このような課題に対処するため、RFC 8305でHappy Eyeballs Version 2(HEv2)というアルゴリズムが定義されています。
HEv2のアルゴリズムは次の通りです。
- DNSサーバへのクエリを非同期で行う
- 解決されたIPアドレスをソートする
- 接続試行を250msごとに非同期で行う
- どれか1つの接続が確立したら他の接続はキャンセルする
解決されたIPアドレスリストやソケットの状態によって次にすべき処理が異なることから、これらの処理をステートマシンで表せることに着目しました。この状態遷移図は2020年のRubyアソシエーション開発助成金ですでに作成されていたようです。(https://www.ruby.or.jp/grant/2020/matsushita_mentor_report.pdf)
この状態遷移図を参考に、コードを実装します。この実装では、Kernel#loop
とcase文が用いられています。今の状態をstateオブジェクトにもち、その値によって異なる処理を行います。
接続中のソケットは書き込み待ちのIOオブジェクトとしてIO.select
の引数に渡されます。そのいずれかで接続が確立されるか失敗するとIO.select
は待機を終了し、そのソケットの配列を書き込み可能なIOオブジェクトとして返却します。
また、名前解決の待機のためにはIO.pipe
が用いられています。スレッド内で名前解決が完了すると書き込み用のIOオブジェクトに書き込まれ、IO.select
の引数に渡されている読み込み用のIOオブジェクトで読み込み可能になります。その結果、IO.select
は待機を終了し、読み込み用IOオブジェクトを返します。
IO.select
の引数にこれらのオブジェクトを同時に渡すことで、1つの式で接続の完了と名前解決の両方を待機可能にしています。
各状態の詳細な処理内容は、セッションスライドをご覧ください。ここでは紹介しなかった、実装中に発生したRFC 8305では定義されていない部分の解決方法なども説明されています。
デモでは、IPv6の接続に長時間かかる場合、従来の接続方法だと接続ができていないところを、HEv2を用いたSocket.tcp
で接続した場合は即座に接続ができたことを見せていただきました。
HEv2を用いたSocket.tcp
はRuby 3.4に含まれるようです。また、Cで実装されたTCPSocket.new
のHEv2対応も現在取り組み中とのことです。
セッションの紹介は以上になります。
RFCで定義された接続方法を実装に落とし込むところはかなり興味深く、非常に勉強になりました。このアップデートにより、接続時のタイムアウトがかなり減るのではないかと期待できますね。リリースが楽しみです。
Embedding it into Ruby code
高久です。私からは@soutaroさんの「Embedding it into Ruby code」をご紹介します!
自社プロダクトへのRBS導入が現実的に考えられそうな内容で印象に残っているため選びました。
このセッションではRBSの型宣言を直接Rubyファイル書けるようにするrbs-inlineを紹介されていました。
従来のRBSは型宣言したいRubyコードとは別に、RBSファイルに型情報を定義する必要があります。しかし1つの処理に対して2つのファイルに記述することは、開発やメンテ、プルリクレビューのしづらさが課題としてありました。
そこでRubyのコードに直接型情報を書けるrbs-inlineの開発を現在進めているとのことでした。
rbs-inlineでは具体的に以下のようにコメント形式で型情報を記述します。
# rbs_inline: enabled class Person attr_reader :name #:: String attr_reader :addresses #:: Array[String] # @rbs name: String # @rbs addresses: Array[String] # @rbs returns void def initialize(name:, addresses:) @name = name @addresses = addresses end def to_s #:: String "Person(name = #{name}, addresses = #{addresses.join(", ")})" end end
YARDに少し似ていますね。YARDの構造に似せて作っているとのことです。しかしYARDタグは構文や型の構文がRBSとは異なるため、別タグとして記載する必要があります。
rbs-inlineはまだ検証段階で最適な構文等を模索中とのことでした。
私自身もWEARにRBS導入を検討したことがあるのですが、まさに@soutaroさんがおっしゃっていたような課題感からまだ導入に踏み切れていない状態でした。特にRubyコード修正した時に、RBSコードも一緒にメンテする必要があるのは気持ち的に少し大変で、メンテが漏れ始めてくると段々誰もメンテしないファイルになる可能性が高いと思っていました。レビューで毎回RBSファイルの修正有無をレビュアーが確認するのも大変です。
それが現状YARDのメンテやレビューが個人的にはなんの苦でもないことを考えると、同じコードに直接書くことで正しくメンテされた状態を維持できる可能性がかなり高くなると感じました。
YARDとどのように互換性を持たせるのかも気になるところで、今後の開発進捗が楽しみです!
Unlocking Potential of Property Based Testing with Ractor
三浦です。私からは@ohbaryeさんの「Unlocking Potential of Property Based Testing with Ractor」をご紹介します!
このセッションはohbaryeさん自身が作成したProperty Based TestingをRubyで実施するためのGemの話と、Property Based Testingのデメリットである実行時間を短縮するために更にRactorを掛け合わせて実行を試みた話の2段階構成となっていました。
Property Based Testingは名前だけは聞いたことがあり、実際に使ったことはなかったので、勉強したいと思いこのセッションに参加しました。
私たちがよく利用しているユニットテストは、開発者が入力値と期待する出力を明示し、コードがその通りに動作することを確認するテストです。この手法は「事例ベーステスト(Example Based Testing)」と呼ばれています。
- メリット
- 入力値と期待値が決まっているのでどのようなテストをしたいのかが理解しやすい
- Property Based Testingに比べると実行時間が早い
- デメリット
- コードが冗長になる
- テストケースの粒度が開発者依存となるため、想定していない入力値によるバグが起こり得る
一方、今回のセッションで取り上げられている「プロパティベーステスト(Property Based Testing)」は、開発者はコードが満たすべき特性や条件を明示した「プロパティ」と呼ばれるものを定義し、ランダムに生成された入力値に対してそれが一貫して成り立つかを確認するテスト手法です。
- メリット
- コード量が少なくて済む
- 開発者が想定していない入力値によるバグもカバーできる
- デメリット
- パターン数やShrinkの実行回数によっては処理時間が長くなる
上記のメリットとデメリットを踏まえて、対象コードの品質をどこまで担保したいかを考え、それに応じて使い分けるのが良さそうだと思いました。
Property Based Testingは、PBTというGemで実装が可能です。
下記はセッションでも紹介されていた具体例となります。
context 'verify biggest method using property based testing' do it 'return biggest number in array' do # Runnerメソッド Pbt.assert do # テストに使用したい入力値の条件を記載(ex.ランダムな整数が入った配列) Pbt.property(Pbt.array(Pbt.integer)) do |numbers| # テスト対象のメソッドを実行 result = biggest(numbers) # 実行結果が仕様通りになっているか検証( ex.配列で一番大きな数値が返ってくる) expect(result).to eq numbers.sort.last end end end end
具体的な入力値は明示せず、Pbt.property
の引数にテストで利用したい値の条件を指定します。実行すると、条件に合わせた入力値がランダムに生成され、テストが実行されます。
デフォルトでは100パターンのテストが実行されます。失敗するケースが検出されると、同じ現象が再現する最小の入力値を探すためのshrinkが行われます。shrinkの実行履歴はPbt.assert
の引数にverbose: true
を設定することで確認できます。
ケース漏れの防止や原因の特定は容易になるものの、テストの総実行回数が多いため、時間がかかってしまいます。
その対策として、Ractorでテストを並列実行させ処理実行が早くなるかを検証されていました。いくつかのテストに対してRactor、Process、Thread、シーケンシャルそれぞれ実行した際のパフォーマンスを比較するというものでした。CPUバウンドなテストに対してRactorは有効でしたが、基本的にはシーケンシャルに実行する方が早いという結果でした。
Property Based Testingとは何かから丁寧に説明されており非常に分かりやすいセッションでした。WEARでも入力パターンの多いコードがいくつか存在するので試してみたいと思いました。
Let's use LLMs from Ruby 〜 Refine RBS types using LLM 〜
小島です。私からは@kokuyouwindさんの「Let's use LLMs from Ruby 〜 Refine RBS types using LLM 〜」をご紹介します!
このセッションでは、プロジェクト全体のRBSをLLMによって生成しようという試みが話されていました。RubyコードからRBSを自動生成する手法は既存でも存在するのですが、それぞれ一長一短があるため完璧なRBSを生成するのは現状では難しく、RBSを生成してから手動で修正することが必要になることが多くありました。そのため、既存手法とLLMを合わせて利用することで手動での修正を必要としないで理想的なRBSを出力させようという取り組みです。既存の手法では型を定義しきれず手動で修正しなくてはならないところ、LLMを利用して修正しているのですね!
今回の発表ではこれらを実現するRBS Gooseというツールを開発したと発表されていました。このRBS Gooseはまだ開発段階であり、実用できるレベルには至っていないとおっしゃっていました。
現段階でのRBS GooseからのRBS生成精度も発表で話されており、RubyコードからRBSを生成する既存の手法に関してはどの手法を使用してもRBS Gooseの出力精度に大差はないが、LLMのモデルの違いによっては大きく精度が変わるようでした。一番理想的なRBSを生成したモデルはGPT-4 Omniだったそうです。そのため、rbs prototype rb
+ GPT-4 Omniの組み合わせが良さそうと発表では話されていました。RBS生成の精度は、今回検証に利用したコードであればGPT-4 Omniを利用した場合でPerfect(意図した型定義がされている)とのことでした。すごいですね!
また、RBS Gooseから生成されたRBSでuntypedのままになっているところに関して、LLMがなぜuntypedのまま残したかをコメントしているものがあったそうです。これはとても興味深い結果だと感じました。
しかし不十分な点もあり、特殊ケースのRBSを扱えなかったりrbs_rails
やtypeprof
などはトップレベルにRBSを生成するので対応が取れないといった課題点を話されていました。
LLMは現在とても注目されていると共に急速に進歩している分野です。RBS GooseはLLMが進歩すればするほど精度が良くなっていくと思われますのでこれからが楽しみですね!
The depths of profiling Ruby
笹沢(@sasamuku)です。私からはスマートバンク社の@osyoyuさんの「The depthes of profiling Ruby」をご紹介します。
本セッションでは、osyoyuさんが開発されたプロファイラ「Pf2」の解説がなされました。
Pf2の特徴として強調されていた点は下記になります。
- マルチスレッドプログラムの解析
- C拡張の呼び出しもトレース可能
- 複数の可視化モードが利用可能
個人的に興味深かったのはプロファイリングを実現する機能群です。それぞれ掘り下げて見てみましょう。
rb_profile_thread_frames()
Pf2でスタックトレースを取得する際に使用されるAPIです。このAPIはosyoyuさん自身によって実装され、Ruby 3.3でリリースされました。
https://github.com/ruby/ruby/pull/7784 https://product.st.inc/entry/2023/12/25/160504
従来から実装されているrb_profile_frames()
はカレントスレッド(GVLをロックしているスレッド)の情報しか取得できないという課題がありました。このAPIの追加により、マルチスレッドプログラムで任意のスレッドの情報を取得できるようになりました。これによりWebアプリケーションのようなIOの多いソフトウェアにおいても正しく全体をプロファイルできます。
TracePoint API
Pf2でGCイベントの受信に使用されるAPIです。実行されるイベント種別を指定し、イベント発生時に任意のコードを実行できます。イベント種別には、クラス定義、メソッド呼び出し、C拡張のメソッド呼び出しなどがあります。
https://docs.ruby-lang.org/ja/latest/class/TracePoint.html
TracePoint APIは任意のイベントを取得できるため、コードリーディングやバグを調査する際に使えそうです。簡単な例を下記に示します。発表でも登場したHash#[]
はC拡張として実装されていますがc_call
イベントを指定することで呼び出しを検知できます。
trace = TracePoint.new(:c_call) do |tp| p tp end trace.enable hoge = { a: 1, b: 2 } hoge[:a] #=> #<TracePoint:c_call `[]' sample.rb:8>
Thread Events API
Pf2でGVLの状態取得に使用されるAPIです。同じ名前のAPIのドキュメントは見つけられませんでしたが、調べてみるとrb_internal_thread_add_event_hook
というAPIが近い機能を提供していました。
プロと読み解く Ruby 3.2 NEWSで次のように紹介されています。
スレッドが停止したり実行可能になったり実際に実行再開したりするときに内部的なイベントを発行して、それをトラップすることでスレッドの挙動を計測できるようになりました。
RubyKaigi 2023の「Understanding the ruby global vm lock by observing it」ではこのAPIを使ったツールの発表がDatadog社のivo anjoさんからありました。
本セッションに参加したことでPf2の特徴を知るだけでなくプロファイラで使用されているAPIに関心を持つことができました。まだ概要レベルしか理解できていないので、今後は実装を読んだり(Cワカラナイ)、プロファイラの動向にも目を光らせたりしていこうと思います!
osyoyuさんが激推ししていたポーたまおにぎりも美味しかったです! 海ぶどうを挟むとさらに最高でした。
It’s about time to pack Ruby and Ruby scripts in one binary
山岡(@ymktmk)です。私からはSTORES社の@ahogappaさんの「It’s about time to pack Ruby and Ruby scripts in one binary」をご紹介します!
本セッションでは、ahogappaさんが開発されたRubyスクリプトとGemからシングルバイナリにコンパイルできる「Kompo」というツールが紹介されました。
開発者のahogappaさんは、Rubyでゲームエンジンを開発しているため、ゲームを配布する際にはバイナリの生成が必要でした。インタプリタ言語であるRubyは、Rubyがインストールされていない環境においても、バイナリを配布することで各環境に左右されることなくスクリプトを実行できるようになります。
これまでにも、Rubyをバイナリにコンパイルできるツールは存在していましたが、様々な問題がありました。
- Ruby本体にパッチを当てている
- Ruby 3.0以上をサポートしていない
- Windows OS上でしか動作しない
同等の既存ツールには、これらの問題があり、それらを解決するためにKompoというツールが作成されました。ちなみにcompose、component、compositeなどの単語から由来しているそうです。
kompoの特徴としては、以下の通りです。
- モンキーパッチのみで、Ruby本体にパッチを当てない
- 一時ファイルへの書き込みがない
- Gemfileをサポートしている
個人的に、Kernel#require
、require_relative
、load
などの内部実装にモンキーパッチを当て、Rubyスクリプトの解釈やC拡張の読み込みこんで、シングルバイナリを生成する手法は非常に興味深いと感じました。将来的にはクロスコンパイルやバイナリファイルの圧縮などにも対応していく予定だそうです。Rubyistにとって、ランタイムの依存なしにスクリプトを実行できることは、CLIなどのワンライナーなプログラムを運用する際に非常に有用です。さまざまな課題があるかもしれませんが、今後の発展に期待です!
スポンサーブースの紹介
前述の通りRubyKaigiの会期直前にWEARのリニューアルを控えていたため、スポンサーブースはDevRelブロックが中心となって準備しました。今回は陸路での移動ではなく空路での移動だったため、スポンサーブースの設営に必要な物品のヌケモレがないよう、いつも以上に慎重に準備を進めました。
また、事前に荷物の遅延が発表されていたため、印刷所から会場直送のパネルや事前に送った備品が届かなかった場合に備えて、会場近隣の印刷所と100円ショップを調べておきました。幸いなことに遅延はありませんでしたが、結果的に100円ショップを調べておいたことは役立ちました。
ZOZOのスポンサーブースでは社内企画「みんなの失敗」のRubyKaigi出張版として、日替わりでRubyKaigi参加者の “失敗” を集め、それを集合知の教訓として、ノベルティのトイレットペーパーと一緒に “水に流して” もらいました。また、リニューアルしたばかりのWEARを紹介し、iPhone実機で「ファッションジャンル診断」や「WEARお試しメイク」を触ってもらいました。
RubyKaigi出張版「みんなの失敗」
RubyKaigi出張版「みんなの失敗」は「付箋に自身の失敗を手書きしてもらう」というやや参加コストの高い企画でした。しかし、実際にはとても多くの「失敗」が集まり「あるある」や「わかる」といった声をたくさん聞きました。
Day 1では、水に流したい “開発の失敗” を挙げてもらいました。
RubyKaigiは国際会議という位置づけのため、日本語話者だけでなく英語話者もたくさん参加しています。掲示していたパネル類はあらかじめ日本語と英語を併記していましたが、その場で書いてもらう付箋はそういうわけにはいきません。そこで都度ChatGPTで翻訳し、ピンクの付箋にtranslateとして英語訳した “失敗” を記載しました。途中から追いつかなくなってしまいましたが、翻訳の効果もあってか英語話者からの “失敗” を集められたのは目論見通りでした。
Day 2では、水に流したい “技術的負債” を挙げてもらいました。
このDay 2はスポンサーブースを巡るスタンプラリーが始まったこともあってか、非常に多くの “技術的負債” が集まりました。そのため、急きょ近隣の100円ショップに走り、部材を購入してパネルを拡張しました。
“技術的負債” として「スロークエリ」を挙げた方が一定数いたのは印象的でした。その特性上、“技術的負債” はなかなか “水に流しにくい” かもしれませんが、いつかは解消したいですね、といった会話もありました。
Day 3では、国際会議らしく水に流したい “i18n対応” を挙げてもらいました。
Day 1、Day 2に比べると挙げにくいテーマかと思いましたが、それでも多くの “i18n対応” が集まりました。
失敗を “水に流せる” トイレットペーパー
“失敗” を投稿していただいた方には、その失敗を “水に流せるように” トイレットペーパーをお配りしました。これは社内企画版の「みんなの失敗」と同様ですが、社内企画版は包み紙がありません。ノベルティとしてお渡ししているトイレットペーパーはZOZOスタッフも持っていない特別版です。その場でご説明した方もいますが、実は “ZOZO” の文字列とZOZOのコーポレートロゴが隠されています。
RubyKaigi 2022の “最後まで身につけているファッションアイテム” としての温泉タオル、RubyKaigi 2023の “一合一会” 米に続き、遊び心を忘れないデザイナーチームによる特別なアイテムでした。
リニューアルしたWEARの紹介
RubyKaigi出張版「みんなの失敗」のパネルがテーブルの2/3ほどを専有してしまいましたが、残ったスペースにモニターを設置し、リニューアルしたWEARの紹介をループ再生していました。
LEDテープでモニターを “デコってある” 姿は技術カンファレンスに似つかわしくないかもしれません。しかし、これは “映え” 目的のものではなく、想定よりも割り当てられたスポンサーブースが暗かったため、少しでも明るくするため準備日のDay 0に急きょ購入して設置したものです。
また、自由に触れるiPhoneを設置し、AIを活用してファッションの「好みのジャンル傾向」がわかるファッションジャンル診断や、ユーザーが投稿したフルメイクデータをARで試せる「WEARお試しメイク」などを体験してもらいました。
ZOZOTOWNのZOZOCOSMEに導入されているARメイク機能は、リップやアイブロウなど、部位ごとのパーツを試せますが「WEARお試しメイク」はフルメイクを試せるのが大きな特長です。メイク関連アイテムとして、手鏡も配っていました。
箱猫マックスとチーコのステッカー
Take Freeのアイテムとして箱猫マックス(Box-Cat Max)とチーコのステッカーを配布していました。箱猫マックスはZOZOTOWNのキャラクターで、チーコはWEARのキャラクターです。
チーコのステッカーは「かわいい」と言ってくれる方が多かったのはとても嬉しいことです。そしてLINEスタンプとして販売している、エンジニアの生態をリアルに再現した「箱猫マックス Vol.6」の中から選出したステッカーはLGTMやDONEが人気でした!
RubyKaigi公式イベントのスタンプラリー
RubyKaigiでは例年、公式イベントとしてスポンサーブースを巡るスタンプラリーが開催されています。このスタンプラリーは参加者とスポンサーブースのZOZOスタッフが会話する良いきっかけにもなっています。参加した皆さんは最後まで集まりましたか?
After RubyKaigi 2024〜メドピア、ZOZO、Findy〜を開催します!
RubyKaigi 2024の興奮さめやらぬ5月28日に、メドピア株式会社、株式会社ZOZO、ファインディ株式会社の3社で非公式アフターイベントのAfter RubyKaigi 2024を開催します!
RubyKaigi 2024に参加した方も、参加できなかった方も、ぜひお気軽にご参加ください!
おわりに
ZOZOでは、各種エンジニアを採用中です。ご興味のある方は以下のリンクからご応募ください。
改めてRubyKaigi運営の皆さん、参加者の皆さん、おつかれさまでした。また来年は松山でお会いしましょう! 現場からは以上です!