メドピア開発者ブログ

集合知により医療を再発明しようと邁進しているヘルステックカンパニーのエンジニアブログです。読者に有用な情報発信ができるよう心がけたいので応援のほどよろしくお願いします。

After RubyKaigi 2023 参加レポート

メドピア株式会社でバックエンドエンジニアをしている徳富(@yannKazu1)です。

先日、RubyKaigi 2023が開催されました。このイベントは、Ruby コミュニティの開発者が一堂に会し、最新のトピックや開発手法について議論する場として知られています。 そのアフターイベントとして、株式会社ZOZO、ファインディ株式会社、そしてメドピア株式会社の3社によるLT・ディスカッション「After RubyKaigi 2023」を開催しました!

今回はそのイベントの様子を簡単にレポートします!

(イベント概要は https://zozotech-inc.connpass.com/event/281473 にて案内してます。)

After RubyKaigi 2023の概要

メドピア株式会社、株式会社ZOZO、ファインディ株式会社3社合同で行うRubyKaigiのAfterイベントです。 昨年度はオンラインでの開催でしたが、今年度はメドピア株式会社オフィスでオフラインとオンラインのハイブリットで行われました。

前半、各社のエンジニアによるLTが行われ、後半に、各社のエンジニアのマネジメントを務める方からのパネルディスカッションがありました。

発表されたLT

  • 「REPLとデバッガを取り巻く環境の変化 -Pry, IRB, そしてdebug.gem」 (メドピア株式会社 古川 健二 @frkawa_)
  • 「ruby.wasm + unloosenでChrome拡張機能を作ってみた」 (株式会社ZOZO 近 海斗さん @Ver3Alt
  • 「そうだ RubyKaigi、行こう。 〜初めてのRubyKaigiの歩き方〜」 (ファインディ株式会社 遠藤 薫さん @aiandrox)
  • 「Road to RubyKaigi Speaker」 (Speakers枠 Go Sueyoshiさん @sue445)
  • 「After RubyKaigi 2023〜メドピア、ZOZO、Findy〜」 (Speakers枠 unasuke (Yusuke Nakamura)さん @yu_suke1994)

パネラー

LTの様子

弊社からは古川にLT枠として登壇いただきました。 古川は医師のクリニック開業支援サービス「CLINIC Support」のサーバーサイドを担当しています。

古川の発表ではタイトルにもある通り、デバッガを取り巻く環境の変化について説明がありました。

デバッグする際には現状だと、pry-byebugが主に使われるケースが多いです。 しかしirbのデバッグ機能もかなり充実してきており、サードパーティーのデバッガーを使わなくても、十分デバッグができます。という説明があった後に、実際にデモンストレーションが行われました。

私自身、pry-byebugを使ってデバッグをしていた人間ですので、irbでのデバッグ機能の充実さにかなり驚いたのと同時に、irbを使ってデバッグする方が楽なのではと感じました。

パネルディスカッション

弊社からはVPoEの平川が参加しました。 今年度のAfter RubyKaigiはオフライン開催ということもあり、お酒とピザが用意されており、乾杯のコールと共に和やかな雰囲気でパネルディスカッションがスタートしました。

RubyKaigiでスポンサーをしてみての簡易的なレポートや今回出展したブースの紹介などが行われました。

メドピアでは、今年度のブースでアルコールパッチテストを行いました。 実は、2019年のRubyKaigiのブースでもアルコールパッチテストを行なっており、なぜ今年度もアルコールパッチテストを行なったかなどの経緯などが話されました。

また、RubyKaigiの話としては、世界中から多くの人が集まり、オフラインで様々な人と話せたのは非常に有益な時間だと感じたと話がありました。

懇親会

懇親会では、各参加者がドリンクを手に、様々な交流を行いました。 特に興味深かったのは、登壇者への質問タイムでした。それは参加者が直接、疑問点や思索に対する答えを得る絶好の機会でした。

そして忘れてはならないのが、参加者が持参した「Rubyメソッドかるた」です。それは既知のメソッドと新たに学んだ知識をテストする楽しいゲームで、全員が大喜びで参加しました。

最後に

これらの素晴らしい思い出と共に、参加者一人ひとりが、この経験を自身のプログラミングスキルや視点に活かし、Rubyコミュニティ全体がさらに発展することを願っています。

次回のRubyKaigiでお会いできるのを心から楽しみにしています。


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。
ご応募をお待ちしております!

■募集ポジションはこちら

medpeer.co.jp

■エンジニア紹介ページはこちら

engineer.medpeer.co.jp

Railsプロジェクトへの「頑張らない型導入」のすすめ

こんにちは。サーバーサイドエンジニアの三村(@t_mimura)です。

主に保険薬局と患者さまを繋ぐ「かかりつけ薬局」化支援アプリ kakariのサーバーサイド開発(Ruby on Rails)を担当しています。

突然ですが!
この度kakariプロジェクトは「型導入」をしました!

kakariのRailsリポジトリに型導入PRがマージされた様子

皆さんのプロジェクトは「型導入」していますか?
「型導入」しているRailsプロジェクトはまだ少ないのではないでしょうか

なぜ型導入しないのか

型を導入すると何かしらが便利になることは分かっているのに何故やらないのでしょうか(煽り気味)

「型の恩恵」と「型を自分たちで書くコスト」の2点を比較していませんか?
RubyKaigi 2023開催前の私がまさしくそう考えていました。
本当にその2点を比較するべきなのかをここで再考してみましょう。

「型導入」とは何か

まず「型導入」とは何を指すのかを整理するために、「型定義」と「型活用」で分けて考えます。

「型定義」について

ここではRuby公式のRBSについて考えます。

github.com

Railsプロジェクトにおいての「型(RBS)定義」の方法は主に5種類あります。

  • ruby/gem_rbs_collectionや各Gemのリポジトリに定義されている型を取得
  • pocke/rbs_railsなどのツールによる型の自動生成
  • typeprofによる型の静的解析
  • rbs prototypeによる型のプロトタイプの自動生成
  • 自分で型を書く

※ 各ツールの詳細はここでは割愛します。

他にもYARDからRBSを生成するツールなんかもあるようですが、現時点で主流なのは上記の5つだと思います。

ここで分かることは「自分で型を書く」以外にも型定義の方法があるという点です。

「型活用」について

次に「型活用」について考えます。

RBSは型が定義されたファイルでしかないため、それを何らかの形で活用しないと意味がありません。

現時点で私が観測できた型の活用方法は以下の3つです。

  • Steepを利用した型検査
  • コーディング時の補完強化
  • KatakataIrbによるirb補完強化

一つずつ深掘りしていきましょう。

Steepを利用した型検査

github.com

「型導入」という言葉を聞いて一番最初に思い浮かべるのはこれではないでしょうか。 型検査による利点は知り尽くされているためここでは省略します。

Railsアプリケーションで型検査を意味あるレベルで運用するにはいくつかのハードルがあります。

まずは、Railsアプリケーションコードに対応する型を手動で定義する必要がある点です。
rbs prototypeを活用しプロトタイプな型の自動生成はできますが、「意味あるレベルの型検査」となると手動での型定義は避けられない現状です。 当たり前ですがこの型定義は「型導入」初回の一度きりではなく、アプリケーションコードの変更の都度更新する必要があります。

「型定義・型検査はそもそもそういうものだから今更何を言っているのか」と厳しいご指摘もあるかと思います。

そこで二つ目のハードルとして、この手動での型定義作業をチームメンバーに強制する点が挙げられます。
一人プロジェクトならまだしも、我々はチームで一つのRailsプロジェクトを開発しているため独断で決めるわけにはいきませんね。
ただ作業を強制するだけでなく「型を定義し活用し続ける文化」を今まで型と無縁だったRubyエンジニアに浸透させるのは簡単な話ではないでしょう。

ちなみに、偉そうに語っている私は自分で型を書きたくありません

つらつらと書きましたが、現時点でRailsプロジェクトで「Steepを利用した型検査」を運用するのはコストが高いと考える人は多いかと思います。

※ 「modelsなど特定のディレクトリのみ型検査対象にする」「型検査はするがCIには組み込まず、エラーも無視して良い体制にする」など折衷案はありますが、今回の話の本質ではないため割愛します。

コーディング時の補完強化

上述のSteepにはLSPを提供する機能があるため、これを補完強化に活用することができます。 他にもRubyのLSPとして有名なsolargraphについてもRBSをサポートしていくと明言しています。

KatakataIrbによるirb補完強化

github.com

KatakataIrbとは型定義を活用することでirbの補完を強化するツールです。 作者のtompngさんによる記事もここで紹介します。 qiita.com

Rubykaigi 2023でも紹介されましたね。 rubykaigi.org

本セッションを聞いたことにより「Steepを利用した型検査」以外の活用方法について意識が向くようになり、kakariプロジェクトの型導入と本ブログの執筆が始まりました。


まだまだRBSの活用方法は多くはありませんが、導入ハードルが高い「Steepを利用した型検査」以外にも活用方法があることが分かります。

また、「コーディング時の補完強化」と「KatakataIrbによるirb補完強化」に関しては、一部のクラス・メソッドの型が定義されているだけでその分恩恵を受けることができます。

つまり「型導入」とは

「型定義」と「型活用」を整理してみました。

「自分で型を書く」以外の「型定義」 + 「Steepを利用した型検査」以外の「型活用」 を採用する選択肢が見えてきました。

この形も立派な「型導入」だと提唱します。
今までの「型を自分で書きたくないから型導入しない」という考えから、「これならちょっとお試しで型導入してみようかな」という気持ちになった人もいるのではないでしょうか。

そんな方のためにちょっとお試しで型導入するための手順を以下に記します。
※ 本ブログ執筆(2023/05/29)時点の情報です。RBS周りは進化が早いため常に最新の情報を参照することをオススメします。

お試し型導入の流れ

ここでは実際にkakariプロジェクトに「型導入」した際の手順をベースに流れを記します。 各作業の詳細については、それぞれの公式ドキュメントなどを参照してください。

0. KatakataIrbの導入

下準備としてKatakataIrbを導入します。

KatakataIrbは「型を活用する」と前述しましたが、型定義がなくともirb補完が強化されるため型に興味がない方にもオススメします。

以下の記事が詳しいためこちらを参照ください。 www.timedia.co.jp

1. RBS Gem導入

ここからが「型導入」です。
まずはともかくRBS GemをRailsプロジェクトに導入します。

gem 'rbs', require: false

production環境では不要なためgroupの指定や require: false をお忘れなく。

2. RBS Collectionのセットアップ

次に以下のコマンドでGemの型定義をインストールします。

$ rbs collection init # https://github.com/ruby/gem_rbs_collection に登録されているgemの型定義を利用するための初期設定
$ rbs collection install # gem_rbs_collectionの型定義を取得

git ignore対象への追加を忘れずに

# .gitignore
/.gem_rbs_collection/

# 型検査を真面目に活用する場合は型定義のバージョンもGit管理下にするべき
# 現時点では型定義のバージョン管理するほど真面目に型を活用していない
rbs_collection.lock.yaml

今回kakariプロジェクトでは rbs_collection.lock.yaml をGit管理下にしない方針にしました。 メンバー間で型のバージョンを揃える必要性は低く、それ以上に「型バージョン更新が無関係のコミットに混ざる」「型バージョン更新だけのPullRequest」が現状ノイズに感じられるためです。

DependabotでのGem更新PRで rbs_collection.lock.yaml も更新してくれる未来が来ると話が変わってきそうですね。

kakariプロジェクトの rbs_collection.yaml が以下です。
meta-tags の型定義が破損しているためスキップしています。 gem_rbs_collectionではなく各Gemのリポジトリに型が定義されているとこういう問題もあるようですね。

# Download sources
sources:
  - type: git
    name: ruby/gem_rbs_collection
    remote: https://github.com/ruby/gem_rbs_collection.git
    revision: main
    repo_dir: gems

# You can specify local directories as sources also.
# - type: local
#   path: path/to/your/local/repository

# A directory to install the downloaded RBSs
path: .gem_rbs_collection

gems:
  # Skip loading rbs gem's RBS.
  # It's unnecessary if you don't use rbs as a library.
  - name: rbs
    ignore: true

  # 型情報が破損しているためスキップする
  # https://github.com/kpumuk/meta-tags/issues/253
  - name: meta-tags
    ignore: true

ここまでの恩恵

ActiveSupportが提供しているメソッドの型定義がKatakataIrbの補完で活用される様子(Before)

Before

After

3. RBS Railsの導入

ここまでででも型の恩恵は得られていますが、更に便利にするためにRBS Railsを導入しましょう。

導入手順はシンプルなので公式READMEをご参照ください。

こちらのPRで対応されていますが、 bin/rails g rbs_rails:install で生成されたrake taskがproduction環境で読み込まれないように考慮する必要がある点にご注意ください。

rbs_rails:all タスクを実行することでsig/rbs_rails配下にActiveRecordが自動生成するメソッドの型情報が出力されます。
それにより以下のように補完が強化されます。

ActiveRecordが自動定義している「アカウントテーブルのemailカラムに関するメソッド」の型定義がKatakataIrbの補完で活用される様子

kakariプロジェクトでは現在sig配下をgit ignoreとしています。「型は自動生成のみ」に振り切っての型導入としているためバージョン管理は不要という判断です。
手動での型定義の運用方法が定まってきたらこの辺の設定は変更すると思います。

4. Steepの導入

一旦型定義についてはここまでとし、型活用に目を向けましょう。 Steepを導入することでコーディング時の型補完が強力になります。

Steep Gemの導入手順もシンプルなので公式READMEをご参照ください。

kakariプロジェクトでは「型検査はしない」と割り切ったためSteepfileは以下の形で利用しています。

D = Steep::Diagnostic
target :app do
  signature 'sig'

  check 'app'

  # 型検査はせずに補完強化用途でのみSteepを利用する
  configure_code_diagnostics do |hash|
    D::Ruby::ALL.each do |error|
      hash[error] = nil
    end
  end

  # https://github.com/soutaro/steep/pull/800 の対応がリリースされたら↓に書き換えること
  # configure_code_diagnostics(D::Ruby.silent)
end

Steepが提供しているLSPを活用する手順はご利用のテキストエディタによって変わります。以下は一例です。

また、RubyMineに関してはSteepのLSPを必要とせずに自前でRBSを参照して便利にしているようです。

VSCodeのSteep拡張を導入して、それとなく補完が強化されている様子

NeoVimにcoc.nvimとSteep LSPを組み込んで補完が強化されている様子

5. 便利Rake Taskを定義

最後に仕上げで日々の運用上便利なRake Taskを定義します。 と言っても、pockeさんがRubyKaigi 2023で発表していたRake Taskをありがたく流用させていただきました。
後述しますが、一部アレンジをしています。

return unless Rails.env.development?

require 'rbs_rails/rake_task'

namespace :rbs do
  task setup: %i[clean collection rbs_rails:all]
  # prototype+subtractを活用したいところだが、自前の型定義が必要になるため保留中
  # task setup: %i[clean collection prototype rbs_rails:all subtract]

  task :clean do
    sh 'rm', '-rf', 'sig/rbs_rails/'
    sh 'rm', '-rf', 'sig/prototype/'
    sh 'rm', '-rf', '.gem_rbs_collection/'
  end

  task :collection do
    # lockファイルに定義されているバージョンに従わず最新の方情報を取得したいためinstallではなくupdateを利用している
    sh 'rbs', 'collection', 'update'
  end

  task :prototype do
    sh 'rbs', 'prototype', 'rb', '--out-dir=sig/prototype', '--base-dir=.', 'app'
  end

  task :subtract do
    sh 'rbs', 'subtract', '--write', 'sig/prototype', 'sig/rbs_rails'

    prototype_path = Rails.root.join('sig/prototype')
    rbs_rails_path = Rails.root.join('sig/rbs_rails')
    subtrahends = Rails.root.glob('sig/*')
                       .reject { |path| path == prototype_path || path == rbs_rails_path }
                       .map { |path| "--subtrahend=#{path}" }
    sh 'rbs', 'subtract', '--write', 'sig/prototype', 'sig/rbs_rails', *subtrahends
  end

  task :validate do
    sh 'rbs', '-Isig', 'validate', '--silent'
  end
end

RbsRails::RakeTask.new do |task|
  # If you want to avoid generating RBS for some classes, comment in it.
  # default: nil
  #
  # task.ignore_model_if = -> (klass) { klass == MyClass }

  # If you want to change the rake task namespace, comment in it.
  # default: :rbs_rails
  # task.name = :cool_rbs_rails

  # If you want to change where RBS Rails writes RBSs into, comment in it.
  # default: Rails.root / 'sig/rbs_rails'
  # task.signature_root_dir = Rails.root / 'my_sig/rbs_rails'
end

kakariプロジェクトは現在ここまで対応しています。 いかがでしょうか、立派に「型導入」しているように見えます?が一つも自分で型を書いていません

すごい勢いでRBS周りのエコシステムが便利になってきているおかげで、自分で型を書かずとも型の恩恵を受けることができるようになっています。ありがたい!

また、本導入方法のアピールポイントとして「型に興味がある人は型を活用でき、型に興味がない人にデメリットがない状態」となっていることが挙げられます。 このレベルの型導入であれば負債になりませんし方針転換も容易です。

「型導入をしない理由」を考えるまでもなく「とりあえず型導入してみる」と気軽に試せるのではないでしょうか。

今後の展望

ここまで自慢げに型導入について解説しましたが、正直なところまだまだ入り口段階だと思っています。

型に対するチームの成熟度に沿って以下の取り組みについて継続してチャレンジしていきたいと考えています。

「rbs prototype( + subtract)」を活用して、独自クラス・メソッドの型のプロトタイプを自動生成

rbs prototypeにより、クラスやメソッドのI/F(引数情報なども含め)が定義されている型のプロトタイプを生成することで、上述の補完が更に便利になることが予想できます。

当初はここまで対応しようと考えていましたが、prototypeで生成した型が不十分でSteepでエラーが発生し今回は見送りました。 実際のところこのエラー自体は一つ一つ解消すれば良いだけの話なのですが、「クラスやメソッド更新の都度rbs prototypeとsubtractを実行し、エラーが発生した場合は対処する」というフローを現段階のチームに組み込むのはハードルが高く感じられました。

僕自身RBS周りはまだ未熟なので過剰にビビっているだけかもしれませんが、今回は「意味のある形で、尚且つ普段の開発速度が低下することなく型を導入する」を目標にしていたため今回は見送ったという経緯です。

typeprofの活用

typeprofを活用するとrbs prototypeより詳細な型情報が自動生成されるため更なる型補完強化が望めます。

解析速度について現状(v1)は課題がありますが、v2で大きく改善されるとRubyKaigi 2023で発表されましね。とても楽しみです!

正直のところまだほとんど触れられていないため、Railsプロジェクトでの解析精度がどの程度か不明な状況です。 型の目的を「型検査」ではなく「補完強化」に振り切っている現状は精度が完璧である必要はないため十分に実用段階かもしれません。

積極的に活用していこうと思います!

Gemの型定義追加を含むOSS貢献

ここまではひたすらRBS周辺OSSの恩恵を頂いているだけなので、私たちもRBS周辺の成長に貢献していくべきです。 gem_rbs_collectionの型定義更新やRBS,Steepなどのツール群への貢献は然り、 RBS Railsのような型自動生成ツールの開発にも興味があるのでチャンレンジしていきたいところです。 (ActiveHash の型定義なんかもうまいこと自動生成したいですよね。)

また、今回のRBS導入とブログ執筆の最中にRBSの些細な不具合を見つけたのでIssue起票しました。

github.com

github.com

このような不具合だけでなくRBS導入のハードルを下げるための提案なども積極的にしていきたいと思っています。
まだまだRBS周辺は貢献できる余地が多く敷居が低い領域なので「OSS貢献」の文化をチームに浸透させる良い機会として精進していきます!

まとめ

以上、型導入の敷居を下げるための記事でした。

「型定義なし」と「型検査バリバリ組み込んでいる」の間の選択について理解し、「ちょっと弊プロジェクトにも型導入してみようかな」と思えてくれたら幸いです。

小さく型導入してみて、チームが型に馴染んできたらもう一歩踏み込んでいく

そんな形で型を徐々に浸透していけると良いのではないでしょうか。

参考記事

本ブログを執筆する際に多くの記事を参考にさせていただきました。 紹介しきれませんが一部列挙します。


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。
ご応募をお待ちしております!

■募集ポジションはこちら

medpeer.co.jp

■エンジニア紹介ページはこちら

engineer.medpeer.co.jp

RubyKaigi 2023 「Reading and improving Pattern Matching in Ruby」 感想

こんにちは、サーバーサイドエンジニアの草分です。
先日のRubyKaigi 2023に参加された皆様お疲れ様でした!

"感想記事を書くまでがRubyKaigi" ということで、今回は1つのセッションを掘り下げた感想記事を投稿します。

rubykaigi.org

このセッションではRubyのパターンマッチの機能を題材に、Rubyの機能の実装を「読んで理解する」そして「パフォーマンスを向上させる」といったプロセスを、いかにして進めていくのか。その方法について紹介されていました。

このセッションはRubyのパターンマッチやメタプログラミングを知った状態で聞くと、より深く理解することができます。 それらの前提知識を軽くおさらいしつつ、セッション内容を振り返っていきましょう。

パターンマッチとは

パターンマッチとは、「データ構造による条件分岐」「構成要素の取り出し」という要素を備えた機能です。

Rubyでは case/in 構文で記述します。
対象のオブジェクトとマッチさせるデータ構造(パターン)を比較し、マッチした場合処理が行われます。

また、マッチした場合はパターンに記載した各変数に値がバインドされ、内部の処理で利用することが可能となります。

User = Struct.new(:role, :name)

def hello(user)
  case user
  in role: 'member', name:
    puts "ようこそ、#{name}さん"
  in role: 'guest'
    puts "ようこそ、ゲストユーザーさん"
  else
    puts "ようこそ"
  end
end

hello(User[:member, "太郎"])  # => ようこそ、太郎さん
hello(User[:guest, "ゲスト"])  # => ようこそ、ゲストユーザーさん
hello(nil)                   # => ようこそ

Arrayパターンについて

複数あるパターンマッチの型の内の1つで、オブジェクトの #deconstruct が配列を返す場合、その配列と指定パターンがマッチするかを検査する機能です。

class Array
  def deconstruct
    self
  end
end

case [0, 1, 2]
  in [0, *a, 2]
    puts a #=> 1
end

※実際にはArray#deconstructはデフォルトで利用可能です

その他のパターンマッチの使い方については過去に解説記事を書いています。 こちらも是非ご覧ください。

tech.medpeer.co.jp

tech.medpeer.co.jp

Arrayパターンを改善していく

Rubyのソースコードは構文解析などによりAST(抽象構文木)に変換され、そこからYARV命令列に変換され実行されます。
実際にソースコードがどう変換されるかは RubyVM::InstructionSequence を使うと簡単に表示することができます。

puts RubyVM::InstructionSequence.compile("puts 2 + 2").disasm
== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,10)> (catch: false)
0000 putself                                                          (   1)[Li]
0001 putobject                              2
0003 putobject                              2
0005 opt_plus                               <calldata!mid:+, argc:1, ARGS_SIMPLE>[CcCr]
0007 opt_send_without_block                 <calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE>
0009 leave

パターンマッチのソースコードがどういうYARV命令に変換されるかを確認すると… とてつもない量の命令が出力されてしまったとのこと。
(本当に量が多かったので本記事では省略)

落ち着いて、ASTをYARV命令列に変換しているcompile.cファイルを見てみましょう。とのこと。
パターンマッチの部分には素晴らしいコメントが記載されており、理解の手助けになるようです。果たして本当でしょうか。

compile.cは1万行をゆうに超える上に、ASTノードの変換を行う関数 iseq_compile_each0 には100近い分岐のswitch文が存在し、それぞれの処理を行っています。

いやはや、何の知識もない状態で読んでも全く理解できないことでしょう。 実際にファイルを開いてみます。 https://github.com/ruby/ruby/blob/8d242a33af19672977dcdcb8d32e9ad547bc0141/compile.c#L6363

compile.c

おお!読めるぞ!?(一部分のみ)

さすがに実際のコードはすぐに読み取れる訳ではありませんが、 読む範囲が定まっており、読みやすいコメントが記載されていれば、なんだかいけそうな気になりますね!(気のせいかも)

改善方法について

Array patternでパターンマッチする場合、検査対象が#deconstructを持つかどうか、その戻り値がArrayかどうかのチェックが実行されます。
しかし対象がArrayの場合は#deconstructの検証は不要なはず、その処理をスキップすれば速度改善になるのではないか。

といった仮説から改善が行われました。

結果、ベンチマークでは速度が2倍に向上!やった!
しかしながら、Array#deconstructがオーバーライドされているケースで問題があったとのこと。

オーバーライドによる問題点

Rubyには「オープンクラス」という機能があります。 既存のクラスを任意の場所で再オープンし、メソッドの修正や追加を行うことができる機能です。 組み込みクラスのArrayも例外ではなく、既存メソッドの挙動を好き放題書き換えることができます。

オープンクラスについてはメタプログラミングRubyなどの書籍を読むと詳しく理解できるでしょう。

前述の改善方法は、Array#deconstructの内容の検証をスキップするものです。 オープンクラスによりメソッドが書き換えられていた場合、検証をスキップすると期待値と異なる動作をしてしまいますね。

さて、その問題に対してはどう対処するかというと……

Do not override Array#deconstruct !

といったオチに繋がるセッションでした。

感想

コーナーケースへの対処の難しさ

Array#deconstructの上書きですが、普通にRubyを使っている場合はおそらく書かないコードでしょう。
Rubyの利用者からすればあまり考慮する必要はなさそうです。

一方で、Ruby言語としては許されている実装です。
言語の改善のためにはそういったコーナーケースへの対処も必要なようです。

「これらを全て考慮するのは大変そうだぞ……」という気分になってしまいますね。

Array patternはArrayだけのものでない

パターンマッチのArray patternはArrayだけのものではありません。
以下のように、#deconstructが定義済の任意のオブジェクトで利用することが可能です。

Spot = Struct.new(:latitude, :longitude)

def latlng(spot)
  case spot
    in [0.. => lat, 0.. => lng]
      puts "北緯#{lat.abs}度 東経#{lng.abs}"
    in [..0 => lat, 0.. => lng]
      puts "南緯#{lat.abs}度 東経#{lng.abs}"
    in [0.. => lat, ..0 => lng]
      puts "北緯#{lat.abs}度 西経#{lng.abs}"
    in [..0 => lat, ..0 => lng]
      puts "南緯#{lat.abs}度 西経#{lng.abs}"
  end
end

latlng(Spot.new(35, 135))   # => 北緯35度 東経135度
latlng(Spot.new(-35, -135)) # => 南緯35度 西経135度

この場合のパフォーマンスはどうなってしまうのでしょう。 チェック処理が増えた影響はあるのでしょうか。 手元での検証はできていませんが、ひとつの事柄を掘り下げていくと次の疑問点も見えてきますね。

おわりに

RubyKaigiのセッションはRubyの内部に踏み込んだ内容が多く、前提知識がないと理解すらままならないこともあります。 ということで今回は、1セッションの前提知識の内容も含めた記事を投稿いたしました。 今後のRubyKaigiのセッションを理解するための一助になれれば幸いです。


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。
ご応募をお待ちしております!

■募集ポジションはこちら

medpeer.co.jp

■エンジニア紹介ページはこちら

engineer.medpeer.co.jp

RubyKaigi 2023 セッションレポート Day3

こんにちは!サーバーサイドエンジニアの伊藤(@yuma_ito_bd)です。

RubyKaigi 2023に参加されていた皆さん、お疲れ様でした。 3日目(5/13)のセッションの中で印象に残った発表をご紹介します。(現地参加したエンジニアの複数人による共同執筆になります。)

Day 1のレポートはこちら tech.medpeer.co.jp

Day 2のレポートはこちら tech.medpeer.co.jp

タイムテーブル

タイムテーブルは以下から確認できます。

rubykaigi.org

Ruby Committers and The World

(執筆:伊藤)

YJITやパーサ、GC、Ractorなどの各トピックに対してRuby 3.3で実装したい機能に関してコミッター同士の公開トークがありました。 Ruby 3.3だけでなく、その次のバージョンに向けた将来的な話もあり、興味深かったです。 5/12(RubyKaigiの2日目)にRuby 3.3.0-preview1がリリースされたようなので、自分でも触ってみようと思います!

www.ruby-lang.org

セッションの最後にはコミッターの方々の写真撮影タイム 📸

Ruby Committers

The Adventure of RedAmber - A data frame library in Ruby

(執筆:貞元)

Rubyでデータフレームを取り扱うRedAmberについてのお話しでした。 RedAmberはApache Arrow の Ruby 実装である Red Arrowと連携し、Rubyらしいインターフェース用意し、Rubyistsにとって扱いやすいデータフレームとなっています。

https://github.com/red-data-tools/red_amber https://arrow.apache.org/

RedAmber on Red Arrow

RedAmberの以下のような機能の紹介があり、メソッド名など使用しやすく感じました。

  • Aggregation: vec.sum
  • Element-wide: vec.cumsum
  • Binary Element-wide: vec > 3
  • pick, drop(列選択, 列削除)
  • slice remove(行選択, 行削除)

Methods

また、RubyKaigiが行われた都市のデータを用意し、都市と緯度経度を結合した内容をプロットした例もあり、より実践的な例でイメージしやすい内容でした。

RubyKaigi Locations

Ruby向けのデータフレームはまだ触ったことがなかったので、これを機会にRedAmberを使用してみたいと思います。

Let's write RBS!

(執筆:千葉)

speakerdeck.com

このセッションでは、RBS 3.1で導入された新機能と既存アプリケーションにRBSを導入する方法について紹介されました。

紹介されたRBS 3.1の新機能は以下の2つです。

  • rbs subtract: 複数ファイルでRBS定義が重複している場合に片方を削除してくれる機能
  • rbs parse: RBSファイルをパースし、Syntax Errorを教えてくれる機能

これらの機能は、既存アプリケーションにRBSを導入する際に詰まりがちな部分を補ってくれるため、今後RBSを導入する予定がある方にとっては非常に重宝する機能になります。

また、既存アプリケーションにRBSを導入する方法については以下の手順でデモンストレーションが行われました。

  1. rbs collectionの導入
  2. steepの導入
  3. rbs subtractの導入
  4. RBSの記述方法

デモンストレーション形式で実際にコーディングを行いながら型についての解説が行われたため、これまでRBSに馴染みがなかった人でも理解しやすいセッションでした。

今回のセッションを通じて、RBS導入への敷居がまた一段と低くなったと感じたため、今後さらに多くのプロダクトでRBSが導入されることが期待できると思いました。

Load gem from browser

(執筆:熊木)

ruby.wasmでサードパーティ製のgemを動かしたいというお話しでした。 ruby.wasmとはRubyスクリプトをブラウザ上で実行できるようにする技術です。

サンプル github.com

現状の課題として、ruby.wasmでは標準gemは使用可能ですが、サードパーティ製のgemが使用できない点が挙げられていました。

その理由は、ruby.wasmのファイルシステムが読み取り専用であり、あらかじめ用意されたgemしか読みこめないためです。 この問題への対応策を考えるに当たり、JavaScriptの歴史が参考になったそうです。その結果、以下の2つのアプローチが提案されました。

  • 実行前にバンドリングして一つのファイルにまとめる
  • import-mapsのように実行時にgemを読み込む

しかし、スクリプト言語の特性を維持したいなどという考えから、後者の方法が採用されることになったようです。

後半では、現在の実装内容や困難な点が紹介されました。

まずrequire_relativeを使ってgemを呼び出す様子がデモで示されました。

デモの様子

現在、Rubyのファイルから別のファイルを呼び出すことは可能ですが、再帰的な読み込みはまだできないことが示されました。 また、ブラウザでファイルをダウンロードする際に使用するFetch APIから返却されるPromiseがRubyではないため、これを適切に扱うのに苦労したとのことでした。

将来的な展望として、ブラウザ向けのプログラミングをRubyでJavaScriptと同等の簡便さで行えるようにしたいと述べられました。 具体的には、UNpkgのような仕組みでgemを実行可能にし、デプロイを容易にし、初心者でも簡単にアプリケーションを公開できる環境を作りたいとのことでした。

発表の中で、何度も「Rubyプログラミングをより楽しくしたい」とおっしゃられていたのが印象的で、 Rubyが楽しく書ける言語として評価されるのは、こういった開発者の方々の努力のおかげだと、強く感じる発表でした!

Ruby + ADBC - A single API between Ruby and DBs

(執筆:内藤)

slide.rabbit-shocker.org

Red Data Tools (Ruby でデータ処理ツールを提供するプロジェクト)で開発されている ADBC(Apache Arrow Database Connectivity) API を用い 大量( 1カラム100万レコード以上)のデータ読み書きを Ruby で実施する話です。

データ交換処理では、通常、シリアライズ・デシリアライズに伴うデータ変換が行われます。 この変換処理時間は少量のデータでは無視できるのですが、大量のデータの場合には無視できない時間になります。

ADBCを用いることで、下記の三つの点から大量データを扱うのに最適化しているとのことでした。

  1. Apache Arrow データFormat を使う:データ交換コストがめっちゃ安い
  2. 結果セットの分割:並列で分割して読み込みを行っているので、高速なデータ読み込みが可能
  3. バルクインサート:高速なデータ書き込みを実施

なお、Apache Arrow データFormat を使うとなぜ良いのかは、下記の資料を読んで下さいとのことでした。

slide.rabbit-shocker.org

アーキテクチャ

ADBC Architecture

Apache Arrow データ形式を返す形で、各種DBにアクセスするための Single(単一形式) API を定義しているため、(Active Record のように)DBを変更してもクライアント側のコードを変更は不要です。

ただし、このAPIはそのままでは Active Recordから使えないので、 Active Record ADBC adapter を開発中(ただ、普段 Active Record を使っていないので、協力してくれるメンバーを募集中)とのことです。

利用可能な Driver は下記になり、MySQLの Driver は存在しないようです。

ADBC Available drivers

Current ADBC

現状、1000万レコードの処理で libpq と比較して ADBC(libpq Driver)は変換のオーバーヘッドがあり、まだ最適化されていないので遅く、ADBC(Flight SQL Driver) はArrow フォーマットを使った高速RPC通信ができているので、2倍速い結果となっています。

Flight SQL(Apache Arrow Flight) は下記の特徴を持っており、Arrowフォーマットのまま(=シリアライズ・デシリアライズに伴うオーバーヘッドが無い)並列転送をしているのがポイントのようです。

Apache Arrow Flight

今回、 PostgreSQL で Flight SQL を話せるようにPostgreSQLの拡張機能を用いたアダプタも作ったそうです。

前述のセッションレポートで述べた RedAmber が ADBC で処理できるようなるとDBに大量データが格納されている場合でも、全てApache Arrow データFormat で処理できる事になるのでオーバーヘッドが無く、高速に処理できそうですね。

Ruby でのデータ処理に興味を持った方は、ぜひ Red Data Tools に参加しましょう!

Parsing RBS

(執筆:伊藤)

rubykaigi.org

3日目のkeynote講演です。 RBSの型定義ファイルで文法エラーが発生していても抽象構文木(Abstract Syntax Tree、以下ASTとする)を取得できるようにパーサを改善したお話でした。

正しいRBSのコードの場合、パーサによってコードに対応するASTを得ることができます。

RBSのコードと対応する抽象構文木(AST)

文法エラー時にツリーを返すように変更

既存のパーサでは文法エラー時は例外が吐かれるように設計されており、抽象構文木を得ることができない仕様でした。 そこで、文法エラー時はMissingTreeというツリーを返却するように変更しました。

これによって、文法エラーが発生していてもASTを得ることができるようになりました。

最後にパースが成功した結果を利用して正しいASTを得る

文法エラーが発生している場合、コーダーの意図とは異なるASTを取得してしまうパターンがありました。

例えば以下のようなConferenceクラスを定義したRBSのコードがあるとします。

class Conference
  def initialize: (String, Integer) -> void
end

そして、以下のようにTalkクラスの定義を(途中まで)追加します。

class Conference
  class Talk

  def initialize: (String, Integer) -> void
end

class Talkに対応するendがないので文法エラーとなっています。

ここでinitializeメソッドはもともとConferenceクラスのメソッドだったのですが、Talkクラスのメソッドとして解析されてしまっていました。

以下のようなイメージです。

- class Conference
  - class Talk
    - def initialize

そこで、最後にパースが成功したASTの結果を用いて、変更があったトークンによるツリーを挿入することで正しくASTを取得できるようにしました。

先程の例では最後にパースが成功したASTは

- class Conference
  - def initialize

なので、そこからclass Talkというトークンが追加されると

- class Conference
  - class Talk
  - def initialize

のようなASTを取得することができます。 このようなアプローチをすることで文法エラーが発生していてもコーダーの意図に近いASTを取得でき、文法エラーであることを適切に表示することができるようになりました。

私は今回のRubyKaigiでパーサに関するセッションをいくつか聞いたのですが、どのセッションでも文法エラー時のエラートレランスについて言及しており、パーサ共通の課題であり難しい部分であることを認識しました。 パーサが改善されることによって、IDEで適切にエラー表示されて開発者体験が向上するので、今後もパーサについて着目してみようと思いました。

おわりに

3日間に渡るRubyKaigi 2023が終了しました。 どのセッションも難しい内容でしたが、普段使っているRubyという言語がどのような実装であるのか、これからはどのような方向を目指して成長していくのか知ることができたので、非常に有意義な3日間でした。

さて、最後のセッションの後のClosingでは、次回のRubyKaigiの日程と場所が発表されました!

次回のRubyKaigiはなんと!!

2024年5月15日から5月17日、場所は沖縄県那覇市です!

次回のRubyKaigiも楽しみですね!

After RubyKaigiを開催します!

最後に、弊社では「After RubyKaigi」を3社共同で今年も開催します! まだご参加登録は間に合いますので是非お申し込みください!

  • After RubyKaigi 2023
  • 概要:RubyKaigi 2023に関するLT・ディスカッション
  • 共催:株式会社ZOZO、ファインディ株式会社、メドピア株式会社
  • 時間:5/18(木)19:00~21:30
  • 場所:メドピア株式会社本社 および オンライン

RubyKaigiで登壇されたスピーカーの方もお呼びします! 詳細および参加登録についてはconnpassよりご確認いただけます。

zozotech-inc.connpass.com

読んでいただきありがとうございました。


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。
ご応募をお待ちしております!

■募集ポジションはこちら

medpeer.co.jp

■エンジニア紹介ページはこちら

engineer.medpeer.co.jp

RubyKaigi 2023 セッションレポート Day2

バックエンドエンジニアの貞元勝幸(@greendrop269)です。
RubyKaigi 2023で長野県松本市に来ています。2日目(5/12)に聞いたセッションについて、いくつか紹介していきたいと思います。

タイムテーブル

タイムテーブルは以下から確認できます。 rubykaigi.org

How resolve Gem dependencies in your code?

How resolve Gem dependencies in your code? - RubyKaigi 2023

RubyGems, Bundlerがどのように依存を解決しているかというお話しでした。

普段、rubyを書く中でgemを使用するためにgem install や bundle installを使用していると思います。 使用する側は、使用したいgemを指定するだけですが、そのgemが依存しているgemも取得する必要があります。 その依存を解決しているResolver Engineは、RubyGems, Bundlerで異なっています。

  • RubyGems: Molinillo
  • Bundler: v2.4からPubGrub
    • PubGrubはDart言語のパッケージマネージャーpubで使用されている次世代の依存解決アルゴリズム

また今後は、RubyGemsもPubGrubになるそうです。

RubyGems, BundlerのResolver Engine

そして、以下のようなRubyGems, Bundlerの例から、複雑さが垣間見れました。

  • C Extensionのgemで、リモートではOKだが、ローカルではNGとなる
  • bundle update --conservative rails で期待通りのgem以外が対象になる

Rubyで開発する中で、RubyGems, Bundlerは当たり前のように使用しており、日々進化していてとても支えられていると感じました。

Revisiting TypeProf - IDE support as a primary feature

Revisiting TypeProf - IDE support as a primary feature - RubyKaigi 2023

現在開発中のTypeProfのv2に関するお話でした。 TypeProfとは、Ruby 3.0から標準搭載されている静的型解析器のことです。 型解析ができることにより、変数の型チェックやメソッドの補完など開発者体験を向上させることができます。

発表ではまず最初にTypeProf v2を使ってVSCodeでの開発デモがありました。

推論された型情報が表示されている様子

推論された型情報が表示されたり、型注釈を記述した状態で型が合わない場合にエラーがリアルタイムに表示されるデモでした。

TypeProf v1では、解析速度に大きな課題がありIDEでのサポートも難しかったようです。

そこで、TypeProf v2では解析速度の向上とIDEでのサポートを目標として再開発することになりました。 v2ではデータフローグラフを用いて、解析結果を差分更新することで、パフォーマンスの向上を目指しました。 Rubyのコードをデータフローグラフに変換し、それを用いて型情報を伝播させることで、型推論を行います。 呼び出すメソッドが変更された際は、変更があった部分のみ新しいグラフを作成してその部分のみ型情報を更新します。

コード変更時のデータフローグラフ

v1ではコードの変更があった際にコードすべてに対して型推論を行っていたのに対し、 v2ではコードの変更があった部分のみ型情報の伝搬を行うことでコード変更時の型推論の速度向上が見込めます。

その結果、解析にかかるパフォーマンスは以下のように大きく改善されたようです。

  • v1: 約3秒
  • v2: 初回は約1秒、コードの変更時は0.029秒

今後の展望としては、キーワード引数や例外などの対応やIDE機能の改善などを行い、Ruby 3.3までに実用可能にすることを目指すようです。

私の感想としてはVSCodeでのデモを見たときにリアルタイムに型情報が更新されてとても驚きました。 最近はGitHub Copilotなどのツールで開発者体験の向上が話題になっていますが、 静的型解析によってより正確により安全に開発することができ、開発者体験がさらに向上するのではないかととても期待が高まる発表でした。

Introduction of new features for VS Code debugging

Introduction of new features for VS Code debugging - RubyKaigi 2023

VS Codeを使用したデバッグの新機能、Trace inspectorとDebug Visualizerのお話でした。

まず、Trace inspectorについてですが、ブレークポイント間の処理内容を保存しておき、それを表示したものとなります。 これがない場合、ブレークポイントから一つずつステップインしながらデバッグすることになり、どのメソッドが呼ばれたかなど忘れてしまうことがあるので、Trace inspectorはとても便利だと感じました。 デフォルトの設定では、呼ばれたメソッドの引数と戻り値を記録していますが、「record and replay」を有効にすることでローカル変数の状態も記録できるようになっていました。

https://github.com/ruby/debug/pull/959

Debug Visualizerは、すでにVS Codeの拡張機能であり、それをRubyに対応させたものとなります。 デモでは、ArrayやHash、NokogiriオブジェクトやActiverecordオブジェクトをグラフや表、ツリー形式などで表示されており、見やすいなものとなっていました。

https://github.com/hediet/vscode-debug-visualizer

Trace inspectorやDebug Visualizerを使用することで、さらに開発者が行う調査や生産性が向上すると感じました。

Optimizing YJIT’s Performance, from Inception to Production

Optimizing YJIT’s Performance, from Inception to Production - RubyKaigi 2023

YJITのパフォーマンス最適化についてのお話しでした。 この中で、印象に残ったものを紹介したいと思います。

まず、パフォーマンス最適化を行うためにYJIT用のペンチマークを準備されていました。 これによりYJIT開発者は簡単にベンチマークを実行できる環境となっていました。

https://github.com/Shopify/yjit-bench

YJIT-Bench

次に、メモリ使用に対するコードGCです。 初期化処理など一度しか実行されないようなものは、マシンコードが開放されていました。

コードGC

ベンチマーク手法やメモリ使用の改善などによるパフォーマンス最適化がRuby 3.2 よりさらに進んでいるので、Ruby 3.3のYJITが楽しみに感じています。

おわりに

私が参加したDay2のセッションでは、IDE向けの機能やYJITなど、今後使ってみたいと思うものが多かったように感じました!
Day3の記事も公開予定なのでぜひご覧ください。


是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

medpeer.co.jp

■エンジニア紹介ページはこちら

engineer.medpeer.co.jp

RubyKaigi 2023 セッションレポート Day1

松本駅にあるRubyistを歓迎する横断幕こんにちは!サーバーサイドエンジニアの中村(@_naka_0)です。

RubyKaigi 2023に現地参加しています。 1日目(5/11)に聞いたセッションの中でいくつかをピックアップしてレポートしていきたいと思います。

タイムテーブル

タイムテーブルは以下から確認できます。 rubykaigi.org

Matz Keynote

Matz Keynote - RubyKaigi 2023

Rubyが生まれてから30年が経過し、そこで学んできた教訓についてのお話でした。

その中でも印象に残った教訓についていくつか紹介していきたいと思います。

良い名前を選ぶ

Rubyは1993年2月24日に「Ruby」という名前が決まったそうですが、この名前が決まる前に「Coral」、「Tish」のような名前の候補があったそうです。 もし、「Ruby」ではなく「Tish」という名前であったら、ここまで多くの人に使われることはなかったかもしれないとのことでした。 この時のことを振り返り、良い名前を選ぶことの重要性を学んだそうです。

最初に決めた基本的原則は保持する

実際にMatzの手元にある一番古いコードは最新のRubyのコードと大きくは変わっておらず、開発当初の時点で基本の部分はすでに完成していたとのことです。

他の人の意見を聞いて、自分の視点では得られない気づきを得ること

1995年12月にRubyをインターネットに公開する前に有志の人に意見を募ったところ、20名程度の人の意見が集まったそうです。 その際、他の人の意見を聞くことで自分の視点では得られない気づきを得ることができたとのことでした。

人と人とのつながりの重要性

2001年9月に行われたJAOOというデンマークのカンファレンスにMatzが参加した際に、当時学生インターン生として参加していたDHH(Railsの生みの親)と話していたそうです。

この後、DHHはRailsを作ることになったそうですが、「この時は後にその人がRailsを作ることになるとは思ってもみなかった」というエピソードが特に印象に残りました。

これらの他にも本の出版や様々な人との出会いが今のRubyにつながっているという素敵な話でした!

リーダーシップとビジョンの提示

Ruby3x3(Rubyを3倍早くする)という目標を掲げた際、Matzは確実にそれが実現できるとは思っていなかったそうです。

しかし、Matzがビジョンを提示したことでMJITやYJITに繋がり、結果的にRubyの性能改善を実現できたそうです。

develop chrome extension with ruby.wasm

develop chrome extension with ruby.wasm - RubyKaigi 2023

本セッションの登壇者の方は実際にruby.wasmを使用してChrome拡張機能を開発してみたそうですが、Rubyらしい書き心地が得られないことに課題感を感じたそうです。

そこで、Rubyのスクリプトを読み込むだけでChrome拡張機能を作成できるようなフレームワークである「unloosen」を開発することに至ったとのことでした。

https://github.com/aaaa777/unloosen

このフレームワークを使用してChrome拡張機能を開発すると、上記リポジトリのREADMEの「Quickstart」に書かれているように少ないコード量とシンプルな構文で拡張機能の開発が実現できるとのことでした。 今後の展望としては、このフレームワークのドキュメント充実化やmanifest.jsonを自動生成できるようにする仕組みを挙げられていました。

また、このフレームワークはGemでは配布しておらず、npmとして配布しているそうです。

https://www.npmjs.com/package/unloosen-ruby-loader

まだ作成したばかりとのことなので、コントリビュートして貢献するのも良さそうだなと感じました!

Power up your REPL life with types

Power up your REPL life with types - RubyKaigi 2023

Ruby irbの補完は便利ですが、メソッドチェーンやブロックパラメータの補完が正確ではない(誤った補完やありとあらゆる候補が出てきてしまう)といった問題があります。 そこで、より正確な補完にするため、irbとRBS(Rubyの型定義を提供する機能)を組み合わせたGemである「katakata_irb」を作ったというお話でした。

「katakata_irb」のリポジトリは以下です。 https://github.com/tompng/katakata_irb

この「katakata_irb」をrequireすることで、メソッドチェーンしてても、ブロック引数・変数などを使ってても、ある程度正しく型を推測して補完候補を出せるようになるとのことでした。 他にはプロダクトにRBSをきちんと書いている場合、rails consoleでもこの補完機能が使えるようになるようです。 また、Gemに型定義がされている場合(rbs_collectionにも型定義されている場合)でも「katakata_irb」の恩恵を受けることができるとのことでした。

既存のプロダクトでも型を定義していればrails consoleでも使えるとのことなので、RBSを導入して型情報を増やしていけると開発体験が上がりそうだと感じました。

Lightning Talks

LT - RubyKaigi 2023

1発表5分で合計12のLT登壇がありました。

その中でも私が一番興味深いと感じた「RBS meets LLMs - Type inference using LLM」というセッションについてご紹介します。

このセッションでは、LLM(大規模言語モデル)を使って型の推測を行うことができないかという発想から、ChatGPTを使ってコードの型を推測する方法をデモを交えながら紹介していく発表でした。

ChatGPTにコードの型を推論させようとすると、出力されるRBSの構文がおかしいことや不要な出力がある等の課題が出てきたそうです。 そこで、Fewshotを用いて意図した出力が得られるように改良していった様子が紹介されていました。

Matz KeynoteでもLLM等で型を推論できないかどうかの話があったため、個人的に今後の発展に期待が持てそうな分野だと思いました。

おわりに

セッションの中では自分の理解が追いつかなかった部分もありましたが、普段は学べないことを学べたり、多くの刺激を受けることができた1日目でした! Day2以降の記事も公開予定なのでぜひご覧ください。


是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

medpeer.co.jp

■エンジニア紹介ページはこちら

engineer.medpeer.co.jp

Rails APIサーバーで Ruby 3.2 の YJIT を有効化してみた。

サーバーサイドエンジニアの内藤(@naitoh) です。

Rails で構築された小規模な APIサーバー(Rails の API モードで構築したもの)で、Ruby 3.2 の YJITを有効化する事で性能アップすることができましたので、喜びを分かち合いたく共有させて頂きます。

shopify.engineering

We’re able to measure real speedups ranging from 5% to 10% (depending on time of day) on our total end-to-end request completion time measurements.

YJIT を開発した Shopify では 5%から10%の処理速度の改善があったという事で、以前から弊社でも本番で運用を開始したかったのですが、比較的検証のし易い APIサーバーで本番投入の準備が整ったので導入を実施してみました。

YJIT 有効化までの道のり

1. Ruby 3.2 へのアップグレード

YJIT は Ruby 3.1 では 実験的機能の位置付けでしたが、Ruby 3.2 以降では正式にサポートされました。

www.ruby-lang.org

  • YJIT は実験段階ではなくなりました
    • 1年以上にわたって本番環境でテストされ、安定して稼働する実績があります。

そのため、事前に Ruby 3.2 までアップグレードする必要があります。

2. YJIT 有効でBuildされた Ruby を用意する。

Ruby の DOCKER OFFICIAL IMAGE は YJIT 有効でBuildされているので、そちらを利用します。

$ docker pull ruby:3.2.2
$ docker run -it ruby:3.2.2 ruby --yjit -e 'p RubyVM::YJIT.enabled?'
true

--yjit オプションで YJIT有効で起動できる事が確認できました。

--yjit オプションを付けないで起動すると YJIT 無効で起動された事がわかります。

$ docker run -it ruby:3.2.2 ruby -e 'p RubyVM::YJIT.enabled?'
false

なお、YJIT無効でBuildされたRuby の場合は下記のようなエラーが出るので、この場合はYJITを利用できません。

$ ruby --yjit -e 'p RubyVM::YJIT.enabled?'
ruby: warning: Ruby was built without YJIT support. You may need to install rustc to build Ruby with YJIT.
-e:1:in `<main>': uninitialized constant RubyVM::YJIT (NameError)

p RubyVM::YJIT.enabled?
        ^^^^^^

※ 補足:Ruby 3.2 の YJIT は Rust-lang で実装されているため、Build 時には Rust-lang が必要ですが、実行時は Rust-lang は不要なため、Docker IMAGE には Rust のパッケージは含まれていませんでした。

3. YJIT 有効化

環境変数 RUBY_YJIT_ENABLE=1 でも YJIT有効化が可能なので、deploy 時の環境変数に設定する事で有効化しました。

YJIT 有効化後と有効化前(1週間前の同一曜日、グラフ破線部分)との比較になります。

Rack Request : p95 Latency 比較 (低い方が性能が良い)

Rack Request

ave: 35.6 ms -> 34.6 ms (2.8% 短縮) とLatency が改善された事がわかります。

Shopify の実績の 5%〜10%の改善に比べると半分程度の改善になりますが、今回はAPIサーバーでフロント周りの処理が無いので妥当かもしれません。

この Rack Request の結果が、Latencyの状況全体を表しているのですが、もう少し詳細を見ていきます。

ActiveRecord Instantiation : p95 Latency 比較 (低い方が性能が良い)

ActiveRecord Instantiation

Ave: 105.8 μs -> 77.9 μs (26.3% 短縮) と、こちらは改善度合いがかなり高いです。

Render Template : p95 Latency 比較 (低い方が性能が良い)

Render Template

Ave: 791 μs -> 701 μs (11.3% 短縮)と、こちらも改善度合いが高いですね。

Action Controller : p95 Latency 比較 (低い方が性能が良い)

Action Controller

Ave: 33.1 ms -> 32.7 ms (1.2% 短縮) と、こちらは効果は低めで、オーダー的にもここが改善されると効果が顕著に出ると思われます。

Request/CPU/Memory 状況

Rack Request 状況

Rack Request Hits

Max は 308 hits -> 282 hits と下がっていますが、Ave 116 hits -> 117 hits と、ほぼ同様なアクセス状況です。

CPU 使用率 (低い方が性能が良い)

Rails Container CPU

Ave 0.195(各コンテナの平均) -> 0.17(各コンテナの平均) とCPU使用率が低下しています。

Memory 使用量 (低い方が性能が良い)

Rails Container Memory

Ave 143(各コンテナの平均) -> 174.5(各コンテナの平均) と Memory 使用量が 22.0% 増加しています。 今回は Memory に余裕があったため大丈夫でしたが、事前にメモリの使用状況を確認してメモリに余裕がある状態でYJITを有効化した方が良さそうです。

まとめ

Memory 使用量が増える可能性はありますが、YJIT を ON にするだけで全体で 2〜3%のレイテンシ改善が見られました。 最新のRuby に追随することで Ruby 開発者の成果の恩恵を受けられるのは非常にありがたいです。

今年のRubyKaigi も直前ですが、どのような発表があるのか期待しております。


是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

medpeer.co.jp

■エンジニア紹介ページはこちら

engineer.medpeer.co.jp