Money Forward Developers Blog

株式会社マネーフォワード公式開発者向けブログです。技術や開発手法、イベント登壇などを発信します。サービスに関するご質問は、各サービス窓口までご連絡ください。

20230215130734

Ruby歴1年弱だけどRubyKaigi 2023に参加したのでレポート

RubyKaigi 初参加!

こんにちは。2023 年 2 月から福岡拠点の Pay 事業本部でサーバサイドの開発をしております古山です。
5/11 ~ 5/13 にかけて長野県松本市で行われたRubyKaigi 2023に参加してきました!

今までこういった技術カンファレンスは敷居が高いイメージがあり参加したことがなかったので
前日まで

  • チケットは買えたけど Ruby 力が低いとそもそも会場に入れてもらえないんじゃないか
  • 会場では突然 Ruby バトルを仕掛けられて、負けたら所持金の半分を奪われてしまうのではないか

と戦々恐々としていました。
振り返ってみると全くそんなことはなく、更に Ruby 歴の浅い私でも参加してみて多くの学びがありました!
まだ参加の興奮が冷めないうちにブログ記事に残しておこうと思います。

会議前日(5/10)

前日に松本入りし、ブース設営のお手伝いへ。

At 松本空港

皆であーでもないこーでもないしながらブース設営するのはお祭り感があってとても楽しかったです。
設営に貢献した感を出していますが、着いた時点で既に 8 割方出来ていて実はあんまり貢献できなかったのは内緒です。

夜には会社で拠点間の懇親会がありました。
色々な拠点から集合したため初めましての方も多かったですが、皆さん松本名物の蕎麦を食べてすぐに打ち解けていました。
私自身福岡拠点からの参加で他拠点の方とは初対面でしたので、こういった機会を前日に設けていただけてとても助かりました!

これは本質情報なのですが、写真の自分達で作るタイプの蕎麦より店員さんが作ってくれた蕎麦の方が定数倍美味しかったです。
松本にいらっしゃった方は出来上がったものを頼んだ方がいいかもしれないですね(松本の蕎麦が悪いのではなく私の混ぜ方が下手だっただけ)

RubyKaigi 中(5/11- 5/13)

ついに Kaigi が始まり、会場に入りましたが人が多い!

人がこんなに多いよ!ということを伝えようと頑張って写真を撮りましたがわかりづらいですね〜全くセンスが感じられない(笑)


弊社ブースにも早い時間から多くの方が来てくださり大盛況でした。すごい!!

Matz の Keynote

初日一発目のセッションは Ruby の生みの親 Matz の Keynote でした。

Keynote 直前。こうやって一堂に会すと人の多さに改めて驚きます。.

今までの Ruby の開発の歴史を振り返るセッション内容で、

  • 1993 年、Ruby の名前候補の中には Tish というものがあり、もし Tish が採用されていたら今頃この会議は「ティッシュ会議」になっていた1
  • Ruby1.9 の時に前方互換性が損なわれ、5 年間もの間 Ruby コミュニティの分断に繋がってしまった
  • Rails の生みの親 DHH は技術は勿論だがマーケティングが非常にうまい人である

と一発目から印象的なエピソードが山盛りでした。


中でも特に、まだ価値をあまり生み出せていなかった Ruby1.0 の時期に、開発リソースを確保するためにSeek Comrades(同志)したというスライドがとても心に残りました。

心に残りすぎてスライドの写真を撮るのを忘れました。😇😇

Ruby の発展を楽しいと思える人達が集まってコミュニティが発展してきたからこそ、今の RubyKaigi の暖かな空気があるのかな。と思いました。(素敵やん)
そういえば、私たちのブースで募ったアンケートも Fun を大切にするエンジニアの方が一番多かったですね!!

また、1 日目のセッション終了後には RubyKaigi Official Party があり、会社を越えた交流がたくさん出来ました。

Matz と普通に写真撮ってる人いてすげ〜!心臓が Forward されている。

実際に 3 日間参加してみて学びはあったの??

とまあここまで見ると「お前は松本まで飲みに行ったんか?」という内容でしたが、会期 3 日間を通して数多のセッションがあり、学びもめちゃくちゃありました!!
いくつか私でもなんとか理解できて印象に残ったセッションについて軽く言及します。

Understanding the Ruby Global VM Lock by observing it

GVL についてのセッションでした。 私のような「GVL ってなんだっけ?」という初級者も理解できるようにすごくわかりやすい丁寧なスライドと共に解説されているセッションで、現地で聞けたのはとても幸運でした。

GVL についての説明が thread.c にあったので、引用します。

A thread has mutex (GVL: Global VM Lock or Giant VM Lock) can run. When thread scheduling, running thread release GVL. If running thread try blocking operation, this thread must release GVL and another thread can continue this flow. After blocking operation, thread must check interrupt (RUBY_VM_CHECK_INTS). Every VM can run parallel. Ruby threads are scheduled by OS thread scheduler.

つまり Ruby VM に対しての 2Global Lock のことを GVL と言うみたいですね!

更にもう少し見てみると、GVL を制御しているのはrb_thread_struct構造体の汎用ポインタで定義されているフィールドblocking_region_bufferで、内部的にはblocking_region_end,blocking_region_beginといった関数でロック操作をしているようです。

 void *
rb_thread_call_with_gvl(void *(*func)(void *), void *data1)
{
    rb_thread_t *th = ruby_thread_from_native();
    struct rb_blocking_region_buffer *brb;
    struct rb_unblock_callback prev_unblock;
    void *r;

    if (th == 0) {
        /* Error has occurred, but we can't use rb_bug()
         * because this thread is not Ruby's thread.
         * What should we do?
         */
        bp();
        fprintf(stderr, "[BUG] rb_thread_call_with_gvl() is called by non-ruby thread\n");
        exit(EXIT_FAILURE);
    }

    brb = (struct rb_blocking_region_buffer *)th->blocking_region_buffer;
    prev_unblock = th->unblock;

    if (brb == 0) {
        rb_bug("rb_thread_call_with_gvl: called by a thread which has GVL.");
    }

    blocking_region_end(th, brb);
    /* enter to Ruby world: You can access Ruby values, methods and so on. */
    r = (*func)(data1);
    /* leave from Ruby world: You can not access Ruby values, etc. */
    int released = blocking_region_begin(th, brb, prev_unblock.func, prev_unblock.arg, FALSE);
    RUBY_ASSERT_ALWAYS(released);
    return r;
}

...

/*
 * ruby_thread_has_gvl_p - check if current native thread has GVL.
 *
 ***
 *** This API is EXPERIMENTAL!
 *** We do not guarantee that this API remains in ruby 1.9.2 or later.
 ***
 */

int
ruby_thread_has_gvl_p(void)
{
    rb_thread_t *th = ruby_thread_from_native();

    if (th && th->blocking_region_buffer == 0) {
        return 1;
    }
    else {
        return 0;
    }
}

GVL については簡単にこれくらいにしといて、セッション内容としては Ruby3.2 より

  • rb_internal_thread_add_event_hook
  • rb_internal_thread_remove_event_hook という GVL のロック獲得にまつわる event の hook 関数が新たに追加されたので、それらの hooks を利用して GVL のロック状況を可視化するgvl-tracingを開発してみた。というものでした。

実際にrb_internal_thread_add_event_hookを使ってるところと、hook によって呼び出される関数は以下のようになっていました。

current_hook = rb_internal_thread_add_event_hook(
    on_thread_event,
    (
      RUBY_INTERNAL_THREAD_EVENT_READY |
      RUBY_INTERNAL_THREAD_EVENT_RESUMED |
      RUBY_INTERNAL_THREAD_EVENT_SUSPENDED |
      RUBY_INTERNAL_THREAD_EVENT_STARTED |
      RUBY_INTERNAL_THREAD_EVENT_EXITED
    ),
    NULL
  );

...

static void on_thread_event(rb_event_flag_t event_id, const rb_internal_thread_event_data_t *_unused1, void *_unused2) {
  const char* event_name = "bug_unknown_event";
  switch (event_id) {
    case RUBY_INTERNAL_THREAD_EVENT_READY:     event_name = "wants_gvl"; break;
    case RUBY_INTERNAL_THREAD_EVENT_RESUMED:   event_name = "running";   break;
    case RUBY_INTERNAL_THREAD_EVENT_SUSPENDED: event_name = "waiting";   break;
    case RUBY_INTERNAL_THREAD_EVENT_STARTED:   event_name = "started";   break;
    case RUBY_INTERNAL_THREAD_EVENT_EXITED:    event_name = "died";      break;
  };
  render_event(event_name);
}

スライド内のデモを visualize してみました。

from = 100000000
to = 0

50.times.map do
  Thread.new do
    while from > 0
      from -= 1
      to += 1
    end
  end
end.map(&:join)

puts "from: #{from}"
puts "to: #{to}"

Ruby の並行処理を理解するためには GVL への理解も求められる。ということを痛感した三日間でした。 今後 Ractor 等並行処理関連の機能がもっとメジャーになってくると思うので、 この gem を使わせてもらって GVL の理解を深めていこうと思います。

発表者の方が登壇資料を twitter で公開してらっしゃいますので、興味のある方はご覧ください。

Gradual typing for Ruby: comparing RBS and RBI/Sorbet

  • RBS,RBI,Sorbet といった型周りのエコシステム
  • Sorbet 向けの RBI 生成ツールTapioca
  • RBS に現状足りない機能(抽象クラス等)

と Ruby の型周り全体についてのセッションでした。

少し用語を整理すると、RBS は Ruby3.0 から標準で採用された型定義のための言語で、.rbs ファイルの下に記述します。
一方 RBI は Shopify さんが独自に開発したもので、"Ruby Interface"の略だそうです。
Ruby コードを使って型情報を記載するので、.rbファイルにそのまま書いても良いし、.rbiファイルに別途分けて記載することもできます。
RBS と RBI は共に型情報を記載するもので、RBS/RBI で書かれた型情報を元に実際にチェックを行うのが Steep/Sorbet になります。

正直に申し上げますと私が今開発しているプロダクトは RBS も RBI も書いておらず、その現状に疑問も持っていませんでした。 このセッションの冒頭で Shopify さんのリポジトリにおける型定義されたコードの比率が示されたのですが、とても高くてすごく印象に残りました。
(またもや写真を撮り忘れてしまったのですが、Shopify さんのブログを見てみると 2020 年時点で既にかなり比率が高いですね)

今まで rbs_collection.yml をとりあえず生成しておく程度の使い方しかしていなかったためセッション内容の後半は正直理解が及ばなかったのですが、 RBS と RBI/Steep と Sorbet の違いについて超基礎的なことを学べたので、サイドプロジェクトや自分の趣味プロジェクトでまずは動かしてみてキャッチアップをしようと思います。

また、Shopify さんのThe State of Ruby Static Typing at Shopifyや、Gradual Typing in Rubyといったセッションも 今回と同じ方が登壇していらっしゃるので、しっかりと目を通しておこうと思います!

最後に

参加前は不安だらけでしたが、振り返ってみると RubyKaigi 2023 は自分にとってとてもプラスになりました。
Matz の keynote から御言葉を借りてきますが、1%の"Just for fun"な動機で Ruby の発展を支えている天才エンジニア達が行うハイレベルなセッションは聞いているだけで勉強になりますし、そういったセッションを通じた「未知の未知」との遭遇という機会は中々業務からは得られない経験でした。 また、普段関わりのない他拠点のエンジニアの方々や、他社のエンジニアさんとも知り合える場で、自分にとっては大きな刺激になりました。

今日からまたお仕事ですが、RubyKaigi ですごく物を作りたい欲が高まった & 得た知見をプロダクトに生かすべくタスクを積む許可を早速頂きましたので しっかり業務に還元していこうと思います。
Ractor 社内勉強会立ち上げるぞ!🔥🔥
Sorbet とか型周りの技術選定するぞ!🔥🔥


最後になりましたが、現地でお話ししてくださった Rubyist の皆さん。ありがとうございました。 来年は那覇開催ということでとっても楽しみです。また来年是非お会いしましょう!! 3 日間お疲れ様でした。

福岡拠点では Rails エンジニアを募集しています。3

hrmos.co

RubyKaigi で得た知識を開発に還元するべく勉強会を立ち上げたり、技術選定を進めていこうと思っています!
来たる 5 月 25 日に天神大名にて弊社も参加する MeetUp もありますので、是非 1 度カジュアルにお話ししましょう!!
nulab.connpass.com


  1. 同様のお話が Ruby 30 周年イベントでもありましたね!
  2. Making MaNy threads on Rubyで言及されたように、Ractor によって GVL が複数存在する場合もあり得るので、GVL の名称変更が検討されています。
  3. 他ポジションも随時募集しております。詳しくは採用情報をご覧ください。