RubyKaigi 2025 参加記録

愛媛県松山市で開催されたRubyKaigi 2025に参加してきました。

今回は、近くに道後温泉があったことやカンファレンス自体が水・木・金開催だったこともあり、月曜日に道後温泉に一泊したりしたので、余裕をもって観光が出来たかなと思います。

沖縄の時はなんだかんだで車が無いと色々厳しいなーという感じだったので、自動車免許持ってない自分にはちょっとやり辛いところがあったんですよね。

来年は函館らしいですが、北海道は土地が何もかも広いので、見て回るには結構大変かもしれないなーとその辺りの点で若干の不安がありますね。

という訳で、今回のRubyKaigiを振り返っていきます。

とりあえず、Day-1からの簡単な記録から。

(人が写ってる写真は基本的にTwitterにツイート済みのものか大丈夫なものを選んでるはず)

Day -1

つまり、月曜日。お昼ぐらいに松山の大街道に到着、荷物を何とか預けて宇和島鯛めしを食べる。 関西人として瀬戸内の魚で育っているので、鯛の刺身が好きな自分としては、この鯛めしはとても素晴らしい。

大街道を散歩して、夏目漱石推しが凄い喫茶店に入ってコーヒー飲んで一息

そのまま、DD4D Brewingに行きビールを飲む。DD4Dは東京近郊のクラフトビアフェスでもよく見る醸造所で信頼感抜群、安定の美味いビールだった。昼から通し営業やってるのも嬉しい。

荷物を回収して道後温泉街にある温泉旅館へ。ビジホじゃない所に泊まるのはめちゃくちゃ久しぶりだった。旅館の内湯に入った後、s01さんの黒暗森林信号に合流しようと思ってたんですが、近場の寿司屋がちょうど空いてたので、軽く寿司をつまんで合流しようと思ってたら、めちゃくちゃなサイズのカワハギが出てきたり、隣の席の人に日本酒奢ってもらったりでタイミングを逃した。

肝の量が尋常ではないカワハギ。延々酒が飲めてしまう。

結果的に、松田さん達(確か武者さんとnobuさんとtomogさんだったかな?)と居酒屋で合流できて、焼き鳥の基本水準が高いとか話していた。

最後は松田さんと、瓶でクラフトビールが入っていて、柑橘やフルーツを使ったカクテルが美味しいバーで飲んでいた。帰ろうとしたぐらいでsorahさん達が入ってきて、会が微妙に延長されたりなどして、RubyKaigiが始まってきたなーと感じた。

Day0

最後のバーで飲み過ぎたせいか、RubyKaigiが始まってもいないのに二日酔いになってしまい、チェックアウト延長して昼から活動開始。道後温泉商店街を散策して、お土産を物色したり、シックな喫茶店でコーヒー飲んだり。そういえば、ペンさん夫婦とStanに会った。

荷物を上手く預けることができなかったので、飛鳥乃湯の休憩室を借りて風呂に入った後お茶菓子飲んでリラックス。こういう時間が得られるのはとても良い。

ホテルのチェックイン時間が来たのでいつも通りビジホにチェックインして、ANDPADさん主催のPre Partyへ。

その後は今回の松山において非常に重要な場所となったThe Tapへ。地域のクラフトビールを提供してくれて夜遅くまでやっている素晴らしいクラフトビアバーである。松山にまた行くことがあれば是非行きたい。

最初、3人ぐらいで入ってたのだが、いつの間にかすごい人口密度になった。流石に申し訳ねえとは思ったが、ここしか無いのでお世話になるしかない。お金は落とすんでよろしくお願いします、という気持ちだった。

Day1

さて、ここからカンファレンス本編なので、セッションの内容を中心に書いていきたいと思う。ちなみに自分の記憶がベースになってるので、内容の紹介は間違ってる可能性がある。ここは違うよって気付いた人が居たら教えてください!

キーノート (ima1zumiさん)

地元開催、コミッタ就任披露、キーノートスピーカーと、今回のRubyKaigiの主役の一人と言って間違いないima1zumiさんによるキーノートは、とても素晴らしかった。

情報の符号化から文字コードの歴史に繋げつつ、文字というものの奥深さとそれがRubyでどの様に扱われているかをとても分かりやすく発表されていた。EBCDICとかはSIer時代にちょっとだけ見たことがあるし、自分がSIerで仕事をしていた時でもサーバーの大半がEUC-JPだったり、何故かUnixのシステム文字コードSJISに変更して運用するとかよく分からないシステム(Windowsシステムとの相互作用のためだと思う)が存在しており、自分は単に面倒だなーと思ってただけなのだが、こういう事をちゃんとどうなってんだこれ?と突っ込んでいけると、こんな風に突き抜けることが出来るんだなーカッコいいなーと思いながら聞かせてもらった。

話の流れがとても洗練されていて、後から聞いた発表までの裏話なんかも踏まえると、大変な労力がかかっているんだろうなということが想像できる。

自分が一番好きだったのは「文字コードにのめり込むことになってしまった」という表現で、この「事前にそうなる未来なんて何も想像していなかったのに、唐突な出会いの結果自分でもよく分からん反応をしてしまって、人生に大きな影響を与えてしまった状態」というのが伝わってくるというか、こういう出会いが今の自分を形成してるんだよな、としみじみ思ってしまった。勝手に滅茶苦茶共感してるけど、多分合ってると思う。

Make Parsers Compatible Using Automata Learning (@makenowjustさん)

正確な名前は忘れましたが、教師あり学習によってオートマトンを生成するアルゴリズムを利用して、LramaとPrismのそれぞれのパーサーのプッシュダウンオートマトンを入手し、オートマトンが等価であるかどうかを比較することで、パーサー間の互換性の向上に繋げようというアプローチについての発表だった。

既存研究によって知られているアルゴリズムを応用して、人間では容易発見できない様な現実の問題の改善に繋げている点が素晴らしい。個別のアルゴリズムについては自分が不勉強なためほとんど分からなかったが、オートマトンというモデルが持つ応用力が伝わってくる話で、複雑な状態遷移を伴う現実の問題に対処する時に、何かしらのヒントになるかもしれない。記憶に残しておきたい話だった。

Continuation is to be continued (@fetburnerさん)

継続という非常にマニアックかつ挙動が理解しにくい機能を用いてモナド記法的な表現を作るという、なんか俺似た様なことやったことある!ってなった発表だった。

自分のアプローチはどっちかというとAST変換に近い形でブロック内で書かれている特定の記法を乗っ取り、flat_mapの入れ子に変換するという魔術だったのが、continuationの方はメソッドの途中でcontinuationを経由して行ったり来たりすることで、flat_mapの入れ子を表現していた。面白いなーと思ったが、continuationで実行されるコード、マジでどこからどこに飛ぶのか全然読めねえw 入れ子になると地獄w

サイン会

パーフェクトRails著者として参加してました。

もう5年前の書籍なのに、2冊買ってくれた方が居てちゃんとサイン出来た!本当にありがとうございます!

今でも役に立たなくはないと思うけど、読み替えないといけないところは色々あるのでその辺り申し訳ないと思う。

来年は新刊持っていきたい……と皆で話してましたが、その辺りはDay2に続く。

かわかみさんが、会場までパRailsを持ってきてくれた!重い本をありがとうございます!

Deoptimization: How YJIT Speeds Up Ruby by Slowing Down (@k0kubunさん)

JITの実装において、Deoptimizationとは何なのか何故こういうものが必要なのかということについてRubyの実例を元に解説してくれる発表だった。

Rubyは一見変化しそうに見えないコードでもユーザーランドのコードで何でも変更が加えられてしまう。あるメソッドの中のローカル変数でさえ書き換え可能だし、定数もちゃんと定数ではないw なのでJITで最適化して置き換えてしまっても前提が崩れた時には元に戻して最適化をやり直さないと結果が壊れてしまう。

という訳で、JITを作る上ではそういった挙動を理解して置き換えを元に戻す緊急ハッチをあちこちに備えておかなければいけない。このdeoptimizationがあることで最適化が出来る範囲を拡大できるが、その分メモリを食うことになる。

この話は3日目のRuby Commiters and the Worldで議論されていたStatic Barrierに繋がっている話だと認識していて、事前にk0kubunさんの発表を聞いておいて良かったと思った。

余談だが、こういうトーク間の繋がりで伏線回収されるの良いよね。

Ruby's Line Breaks (@spikeolafさん)

パーサーギャング団のボス、kaneko.yさんの発表。

Rubyの改行とは、改行を認識する・無視する状況とは何ぞやということを実際のRubyのコードとそのパースのされ方から紹介しつつ、歴史的経緯によって発生した基本規則からの例外事象をいくつか上げて、Rubyの改行の認識のされ方が実はlex_stateというlexerが持っている状態によって分岐しているという話だった。

a = 1..
2

みたいなコードは実際に直感的ではないし事故りそうな気はする。

この発表の凄いところは、改行というのは一例であってlex_stateというものに依存して状態が過剰に複雑になっていることが大きな問題の一つであり、それを観測可能に出来る様にLramaを回収したという点だと思う。なので、今後改行に限らずlex_stateに依存している例外的な挙動を補足し改善するための足がかりを確保したということだ。確実に新しい一歩を進めている偉業をサラっと発表している辺りがCoolだなと思った発表だった。

State of Namespace (@tagomorisさん)

我々もMatzも待望しているNamespaceの実装に至るまでの苦労話(絶賛継続中)。

k0kubunさんの発表に近いところがあって、Rubyってのはマジで色々なところが実行中に書き変わる言語なので、下手にメモリ構造を共有すると効率的であっても何が起きるか分からん、みたいなことが大量にあるのだなあと思った次第。Rubhという言語は使うのは自由だけどそれ自体を開発するのはすごい大変という対比がよく分かる話だった。

C拡張のdlopenの重複回避のためにdllをコピーしてる箇所の凡ミスが面白かったw

TRICK

体力が尽きてきたので最後の通常セッションはお休みして、TRICKへ。SQLiteパーサーとクエリ生成の話は聞きたかったけど……。

TRICKはもう言葉では説明できないので、動画が上がったら見てくれとしか言い様がない。

言えることとしては、今年もぺんさんとmameさんが異常な活躍を見せる中、優勝者は別の人だったのが驚いた。

個人的に一番好きだったのは、ぺんさんの標準入力で数字を打ち込むとirbのsyntax highlightで表示できるアスキーアートになり、アスキーアートを上から順番に実行すると算術演算した結果のアスキーアートが出てくるやつ。文章で書いても何言ってるか全然分からんし、何がどうなってんねんという感じ……。

何にせよ、今年も滅茶苦茶笑ったので最高だった。

Official Party

滅茶苦茶広い松山城の城山公園でOfficial Partyがあった。天気が良くて本当に良かった。DD4Dのビールが何と飲み放題であり、ここだけクラフトビアフェスか?みたいな状態になっていた。

食べ物は結構数がシビアで食べ損ねた人が結構居た様だが、日本酒とビールは豊富だった。 どうやら、終了時間の少し前ぐらいに日本酒もビールの樽も綺麗に無くなったらしく、奇跡的な見積り量だと驚いた。松田さんはやっぱ何か持ってる人だよなと思う。

The Tap

帰り際にトイレ行ってたら飲んでた面子と逸れてしまったので、The Tapに向かうことにした。SHさんが一人で飲んでたのでしばらく二人でサシ飲みみたいなことをしてたら、やはり後からぞろぞろ人がやってきたw

結構SHさんとガンダムトークが出来て楽しかった。

The Tapでydahさんの熱い思いを聞きつつトークの予習が出来たなーと思ったのだが、それがあったのはDay0だったかもしれない。大概ビール飲んでるので細かい記憶が曖昧になっている。

松山のラーメン・つけ麺、美味くない?

Day2

二日酔いはそこまで重くなかったが、睡眠の質が悪過ぎて朝からクソ眠かった。とりあえずキーノートにはギリギリ遅刻せずに済んで良かった。

キーノート (Ivo Anjoさん)

事前にdatadog gemのコードを多少読んでたんだけど、非常に洗練されており、TracePointをめちゃくちゃ使いこなしているなーと思っていたので、とても楽しみにしていたキーノートだったが、期待以上だった。思ってたよりTracePoint成分が多く、Cレベルでしか触れないイベントやpostpone_job APIなどマニアックな機能をプロファイラ用のツールキットとしてまとめて使い易い形でgem化しているという話だった。

一つ一つの機能の説明が非常に分かり易く、話し方自体もめちゃくちゃ洗練されていて、朝一の英語セッションだったけどとても聞き易かった。

自分の仕事でも結構ガチでパフォーマンスに向き合わなければいけないことが多いので、楽しい + すぐに役に立つという俺得な発表だったと思う。

Dissecting and Reconstructing Ruby Syntactic Structures (@ydah_さん)

Rubyと他の言語のメソッド引数の解釈の仕方を比較しつつ、Rubyの特徴的な文法構造の一つとしてargsとprimitive(primitiveだっけ?記憶だけだと限界が……)という階層に着目してRubyという言語の柔軟さと文法構造の関わりというものを熱く語っているトークだった。

そういった文法構造を可視化しよりメンテしやすくする仕組みとしてparameterized ruleという仕組みをLramaに導入したということ、そしてそこで得られた成果を楽しそうに話すydah_さんが印象的だった。

熱量があり過ぎて、ちょっと聴衆を置いていって感があったけど、それもRubyKaigiって感じで良い発表だった。(自分も事前にThe Tapで色々聞くタイミングがあったので着いていけたかなーぐらいw)

ZJIT: Building a Next Generation Ruby JIT (@maximecbさん)

突然表れた新たなJIT、ZJIT。スケジュールに書かれてたabstractを見る限りではコンパイル結果のシリアライズと共有を可能にするのかなーぐらいの話だと思ってたら、JITの仕組み自体を根本的に方針転換するという話だった。

2年前にMaximeさんはキーノートをやっているとは言え、正直、これがキーノートではないRubyKaigiは恐ろしいなと思った。結構な大ニュースである。

YARVに注目してLBBVというまだ研究が進んでない分野で一定の成果を出したけど、これ以上の高速化はかなり厳しいという観測結果が出てきており、メソッドベースの教科書的なJIT手法に転換するとのことだ。自分達が作って切り開いてきたものにスパっと見切りを付けて、今迄の経験と挑戦とリスクを評価して、よりスタンダードなアプローチで結果が出せると判断し活動を開始している点がとても凄いことだと思う。何より作ってきたものを捨てるのは簡単じゃない。

既にマイクロベンチではYJITと同等以上の成果が出ているらしく、年末には実用的な性能としてYJITと同様のレベルに辿り着くと計画しているらしい。開発のペースが早過ぎる。どうなってんだ本当に……。

という訳で、度肝を抜かれた発表としては今回この発表がベストだったかも。次元の違いを見せられた感がある。

そういえば、またハチロクがスライドに居た。やっぱJITチームでは頭文字Dは必読なのだろうかw

ランチタイム

2日目のランチは、会場に来ていたパーフェクトRails関係者で集まってお寿司を食べた。

まだ未来はどうなるか分からないのだが、来年には既刊ではなく新刊を持ってRubyKaigiにサークル参加したいぞ!という気持ちを新たにする会だった。

前日のサイン会でやる気も出てきたので、なんとかKaigi Effectで動き出せる様にしたいなと思っている。

MicroRuby: True Microcontroller Ruby (@hasumikin)

午後一のセッションはちょっと時間が中途半端になってしまったのでお休みして、スポンサーブースを眺めたりして、その次から再開。

はすみさんのトークは淡々と凄いことを話すのが面白いところだなと思う。mruby/cではなくmrubyのフットプリントを十分に小さくし、更に組込みでよく利用される非同期ジョブのAPIを自分で実装することで、mrubyを本当の意味でマイクロコントローラーとして使える様にしたという発表だった。

mrubyとmruby/cで互換を取るためにサラっと凄い量のコードを書いていて、本当に凄かった。mrubyという言語の活用幅を大きく広げるプロダクトを作り上げたという点でも偉業だと思う。(自分は物理的な方面では非常に不器用で面倒臭がりなので、こっちの方面のプロダクトを全然触っていないのが申し訳ないところである……。なんかMatzもそうらしいがw)

Speeding up Class#new (@tenderlove)

Class#newはinitializeを経由することでCとRubyのインターフェースを行き来することになってしまい、そこで発生しているオブジェクト変換のオーバーヘッドが無視できないので、全部Rubyにした方が早いんじゃない?という話だった。特にキーワード引数はHashオブジェクトを経由しないとCとRubyを行き来できないのでコストがかかるらしい。

仮説をちゃんとベンチマークで検証してゴールに向かう開発の仕方や、Rubyのメソッド呼び出しにおけるinline cacheについても詳しく解説されており、とても勉強になる話だった。

JITが実用的になってきた時に、k0kubunさんがRubyで書いた方が簡単でかつ早くなる未来が来るという話をしていたが、この辺りもJITによる最適化のメリットが大きそうだし、その辺りも考慮に入っているのだろうと思う。

Making TCPSocket.new "Happy"! (@coe401_)

RubyのSocket.tcpに実装したHappy Eyeballs V2をC実装であるTCPSocket.newにも展開するという話だった。メンターであるakrさんの指摘を素直に受け止めて、そもそものSocket.tcpの実装をやり直すところから始めており、こういう真摯さがしおいさんの凄いところだなと改めて思った。

そして、相変わらずスライドの作り込みが凄い。しかもこれをちゃんと締切に間に合わせるレベルで作ってるんだから偉過ぎる……。これは根本的に駄目人間な自分には真似できない。

しっかりとRubyKaigiまでに実装を完了させ、実際にRubyのネットワーク接続を大きく改善してコミッタに就任というのが実にカッコいい。最悪のケースだと100倍ぐらい接続までの時間が短くなるので凄い成果である。

LT

なんか今年のLT、音出る発表多かった気がする。やっぱ音はインパクトありますね。

ダブルヘッダーだったydah_さんのLTが実にテックLTって感じでとても好きだった。デモやろうとして間に合わないところとか特に良い。

今回、kakedaさんがLTの前説を務めており、LTの歴史や銅鑼の歴史について話をしてくれたのがめちゃくちゃエモくて最高だった。自分はコミュニティに顔を出し始めたのは、その黎明期よりも少し後のことで活動時期が被ってないので、kakedaさんのことは名前だけ存じ上げているという感じだったが、今回語ってくれたLTの文化はまだ強く残っていたというか、わちゃわちゃやって熱量先行で全然間に合わねー!みたいな感じをとても楽しんでいた世代だ。この人時間内にちゃんと言いたいこと話し切れるかな、というプリミティブな所で登壇者を応援したくなる、そういうところがLTの好きな点だ。時代の移り変わりで登壇する人も昔より大分多様になったので、こういう昔ながらのLTというものを意識するかしないかは個人の自由だと思うが、自分はそういうLTが好きだし今回はそういう話がいくつかあってとても楽しいLTだったと思う。

kakedaさんのブログでそういった歴史についてもう少し詳しく説明している記事が上がっていたので、是非読んで欲しい。自分ぐらいの世代だとエモさ爆発だった。

note.com

晩御飯とRuby Karaoke

2日目はドリンクアップからあぶれていたというか、こじんまりと飲みに行くのもいいなと思っていたので無理に補欠をどうにかしようと思わなかった。という訳で久しぶりに会った@shishiさん、ペパボの@deowさん、弊社若者の@_anji7さんとで地元っぽい居酒屋で晩御飯を食べた。

やっぱこういう地元感のあるお店の刺身のコスパが異常というか、いやこの美味さとこの量で1800円ですか?となる。後やっぱ鶏肉が美味い。

小さい集まりだったが今回RubyKaigiに初参加した若者をコミュニティとの接点に紹介できたのは良かったかなと思う。

食事の後は自分は離脱してRuby Karaokeに移動。ちなみに酒飲んでカラオケすると大概酷いことになるので、2日目はほとんど飲んでいない。

mishさんのリクエストで一人ヒプマイやったり、大部屋でCreepy Nutsを歌ったりしていたが、個人的なハイライトはima1zumiさんとSOUL'd OUTを歌う実績が解除できたことだ。最近スティール・ボール・ランのアニメ化が発表された結果SOUL'd OUT熱が高まってきているが、結構昔のグループだし知らん人は全然知らんみたいなグループなので、着いてきてくれる人が少なくて普段寂しい思いをしていたが、今回キーノートスピーカーと一緒に盛り上がれたのは最高だった。COZMIC TRAVELをカラオケで歌う人とか高校時代の同級生以外でほとんど見たことがないw

自分は割とガチめでカラオケが趣味なので、普通よりそれなりにカラオケが上手いらしく結構人に褒められるのだが、好きなことを褒められると正直嬉しいw 人間調子に乗ってはいかんと思うがこの辺りは素直に喜んでおきたい。

Day3

Ruby Karaokeのおかげで酒はほとんど飲んでなかったので二日酔いは一旦抜けたのだが、HPが足りてない感じは変わらず。とりあえず遅刻しなくて済んだのでセーフ。

Ruby Committers and the World

and the Worldは非常にライブ感が強いセッションなのだが、今年はnobuさんのパッチ袋が火を吹いていて面白かった。やっぱパッチモンスターが猛威を奮っている回は面白い。k0kubunさんのセッションの感想でも書いたがStatic Barrierの議論が中々興味深かった。deoptimizeの話を聞いた後だとその意義というものがよく分かるし、斎藤さんがコミュニティの分断を招くからそう簡単に有効にできないと言ってたのもよく分かる。後、inline commentは結構欲しいですね。rbs-inline以外の文脈でもここに軽くコメントで注釈書きたいんだけど上の行に書くかーみたいな時はある。まあ、改行してしまえば書けるとかもあるんだが。

ランチ & 道後温泉

くっ、ガッツが足りない!という状態だったので、ちょっとでもスッキリさせようと思って午前のセッションはお休みして早めに抜けてモリスさんと道後温泉に向かった。早めに移動したのでスムーズに御飯が食べれたし、そのまま道後温泉本館で体を休める。温泉の体に染み入る感じと重力から開放される感じが良いよね。温泉街が近いRubyKaigi最高では。

温泉から出たら、道後温泉の湯上がりIPAを一杯。ってかフルサイズがでけえ。UKパイントでもなくて大ジョッキだよこれは。

天気が良くて、建物が映える

Road to Go gem (@sue445さん)

Goでrubygemsを作る基盤を整備したという発表。事前に聞いていたのだが、自分が関西RubyKaigiで話しをしたRustでrubygemsを作る話が参考になったらしく、これは聞いておかねばという感じで着席していた。

自分は一人のユーザーとして紹介しただけではあるのだが、それがRubyKaigiでの発表に繋がってたのかと思うと、発表して良かったなと思うしとても嬉しい。

実際、Rustの方でソースコードを読んでいたので構造についても想像が付くのだが、それを自分の手でしっかりやり切って形にしているのは凄いことだと思う。正直、かなり面倒臭いことが一杯あったはず。

Bundlerの方にもPRを出してるらしいので、近い内に皆の手元でGo製のrubygemsを書く雛形が簡単に手に入る様になるだろう。

Analyzing Ruby Code in IRB (@tompngさん)

スポンサーブースを眺めてたら、ちょっと遅れてしまったので最初が聞けなかった。IRBをPrismのAPIに寄せて、そのfault-tolerantな性質を利用してIRBがカバーできない変なコードもちゃんと扱える様にしよう、という発表だったと思う。

印象に残っているのが、IRBが受け取るとヤバイ変なコードがマジでおかしなコードで、この辺が如何にもぺんさんっぽいというか、今回も楽しませてもらった。

去年のレポートでも書いたけど、本当に想像力の幅が凄い。想定範囲の広さってエンジニアの能力としても重要な要素なので、リスペクトしかない。そして真似できる気がしないw

力尽きてホテルへ

眠気が限界だったので、短時間でも仮眠しようとホテルに戻った。40分ぐらいベッドで横になってキーノートへ向かう。

Matz Keynote

登場の仕方がズルいww あんなん笑うやろw

奈落からスポットライトを浴びながらせり上がってきたMatz

AI時代のプログラミング言語とは、というテーマでのキーノートだった。「AIのための」ではなく「AI時代」に則したプログラミング言語の話であるのがポイント。「AIのための」言語?だったらPython使っとけばいいんじゃない?みたいなノリが黒Matz感あって面白かった。

実際のところ、短い記述量にコンテキストを多く含められ、DSLを構成するのに非常に親和性のあるRubyという言語は、LLMとの相性は悪くないだろう。型によるコードの意味の詳細化は、一定以上複雑なシステムと人間の認知力の関係性においては有効だと思っているが、LLMをベースにした今のAIの能力にそこまで有効かどうかはまだよく分かっていないと思う。人間に親和性のあるインターフェースとAIに対するそれとで、どういうバランスを取るのが良いのかはこれからより明らかになっていくだろうし、自然言語と今のプログラミング言語の間の存在としてAIとのコミュニケート言語が主流になってくるかもしれない。そういう時代においてRubyは割と戦い様がある言語なんじゃないかというのは自分も同意するところだ。まあ世の中どうなるか分からんというのは毎年のことなので、信条を守って進む以外にやれることはないのかもしれない。

Closing

書きそびれてたので追記。 s01さんが本当にやり切ったという感じで壇上で挨拶していたのにグッときた。Offical Partyは実際天候のリスクとかもあって自分の想像より遥かに大変だったろうと思う。

終わった後にスタッフに感謝の拍手が出来るあの瞬間はとても好きなので、スタッフの皆は堂々と壇上に上がってくれ!といつも思っている。

After Party

カンファレンス後は、さくらインターネットさんのUchiageへ。めっちゃ結婚式会場だったw

入口でウェルカムしているtagomorisさんの図

色々話したんだけど、自分ももういいオッサンになってる訳で、久々に会った友人と話す仕事の苦労話みたいなのが段々重くなっているのを実感するw

tricknotesさんと話してる時に、これこれこんな感じの仕事があってこういう時にマジで大変ですよねー、みたいなことを話してたら「自分の仕事見てました?」とか言われたのが面白かった。一緒にmirakuiさんと小川さんが居て、CTOと元CTOみたいな感じの人間が集まってたので、皆の分かりみが深いというやつだったのが印象深い。

uchiage後は、そこそこの人数と合流して「生」というビアバーに入った。ここも妙に地元感があるというか、えらく家庭的なビアバーだった。パーサーギャングやkoicさんが居たので登壇の話を色々と聞いたりしていた。

そして、最終的に松山の本拠地とも言えるThe Tapに移動。入ったタイミングではそんなに居なかったのだが、なんだかんだでじわじわと人が増えて最終的に黄色いシャツの一団が出来ていた。豪華メンバーが揃ってくるのRubyKaigiって感じでとても良い。

koicさんとydah_さんとで、最後まで飲んでる俺らみたいな人間ってのはつまり寂しがり屋なんですよ、っていう話をして意気投合していた。その時、松田さんは居なかったけど同じだよなーと思っている。

で、3日目でHPが限界だった所で最後まで飲んでたのもあり、店を出るタイミングでかなりベロベロになってしまっていて、koicさんとydah_さんとpastakさんに助けてもらった。記憶が曖昧だがコンビニでポカリと水を買ってきてもらって、水分ガンガン取って休んでたら多少なんとかなったらしい。

気付いたら着替えもせずにベッドでぶっ倒れてたが、幸い何も失くしていないし事前にチェックアウト時間を延長してたおかげで普通にチェックアウトできた。本当お世話になりました、ありがとうございます!

Day4

二日酔いではあったが、一応それなりに寝れたので普通に観光するぐらいなら問題なさそうってことで、お昼過ぎに松山城に向かう。リフトでお城近くまで上がれるのだが、下のフェンスまでそれなりの高さがあり大人になってこういうの乗ると結構怖いなと思う。自分はそこまででもなかったが、harukasanのツイートを見ると顔が割とマジだったので人によってはかなり怖かっただろうと思う

松山城は今迄見たことがある中でも、最高レベルに戦闘的な城で、道の作り、門の数、城の窓の配置と角度、全てが殺意に溢れており見てて滅茶苦茶楽しい城だった。そして、マジでRubyistだらけだったのであちこちで立ち話が出来てお得な場所だったw

中の展示も色々見所があったし、天守閣からの眺めが滅茶苦茶良くて瀬戸内海まで見えた。松本城の時は自分のタイミングが悪くて曇ってたので、その無念が晴らせた感じ。

その後、鯛ラーメンと鯛めしのセットを食べた。観光地っぽい店だったが普通に美味かったし東京のラーメン屋より全然安い。物価の違いを感じる。

実質RubyKaigiの廊下と化していた大街道をブラブラ散歩してすれ違ったRubyistに挨拶をしつつ、生みかんジュースを飲む。今回何度か飲んだ中でお気に入りだった「せとか」のジュース。味が濃いんだけど結構スルスルと飲める感じで上手い。完全に生物なのでお土産にできないのが残念な感じだった。

そして、Day-1とOfficial Partyでお世話になったDD4Dで今回の旅最後のビールを飲む。お店の人に最高でしたと感謝を伝えたら、綺麗に飲み切ってくれてこちらこそありがとうと感謝が返ってきた。イベントで持ち込むと大抵余ってしまうらしいが、そこはRubyistの酒飲みパワーでしっかり飲み切ることができた。後から聞いた話では向こうとしても大分良いイベントにできたらしく、何だったら全国に遠征しますよ、ぐらい言ってくれたらしいw

最初は一人だったんだけど、ここでもじわじわ人数が増えてきて最終的にはこんな感じだった。

面白かったのがioquatixさんがDDRプレイヤーだったらしく、やっぱコミッタ界(一部)ではDDRが流行ってるのか?とか思ったりした。せっかく日本に来たから日本でDDRをプレイしたいとのことで、日本のコナミのアミュパスとアカウントを紐付ける登録作業を手伝ったりしていた。実に光栄なことだw

この後は荷物を回収して空港へ。バスで炬燵さんに会って「ここ座れよー」って言われたりしつつ、空港でお土産を探していた。

実家の母親向けに一六タルト、後は酒飲みセット。

腹ごなしをしようと寿司屋に入ったら黒曜さんとばったり遭遇し、ここぞとばかりにモバイルバッテリーの電力を借りたりしていた。あの時は助かりました。ありがとうございます!自分のはDay3で飲み過ぎてぶっ倒れてたせいで充電しそこねていてモバイルバッテリーのHPが0だった。

帰りの飛行機がogijunさんと同じでヤノさんを紹介してもらったりした。最後までRubyistとの遭遇が楽しめるのがRubyKaigiの良いところだ。

まとめ

色々とあったことをサクっと思い出せる範囲でまとめて書いてみましたが、今年は観光もカンファレンスもバランスよく楽しめたかなーという感じです。沖縄の時は直前まで体調崩していて参加自体が微妙だったのと、移動に自動車が必要そうな箇所も多かった感じがあって、全力で楽しめたかというとちょっと悔いが残るところがあったので、今回その分は取り戻せた感じがありますね。

実は高校生の頃に一度道後温泉に行ったことがあって縁のある町ではあったんですが、今回改めて松山が好きになった。繰り返しになるけど松山城は本当に良かった。地元の人(キーノートスピーカー)がオススメするだけのことはある。

最近、RubyKaigiのCFPに応募できるレベルでRubyと関われていない自分ではあるのだが、やはりRubyKaigiはとても楽しいし刺激も受けるし勉強にもなる。自分にとって最高のカンファレンスがこれからも続いてくれることを祈っております。

来年の函館もとても楽しみですね。Rubyistの皆様、また函館で会いましょう。RubyKaigi 2025、お疲れ様でした!

オブザーバビリティ入門: OpenTelemetryについて知っておくべきこと

自分が在籍している会社でKafkaを利用したマイクロサービスが増えてきているので、昔からオブザーバビリティの向上というものにちゃんと着手したかったのだが、最近になってやっと手を動かせる所まで優先度を上げられた。

という訳で、ここしばらくは社内にあるマイクロサービス群にOpenTelemetryによる計装を入れまくっている訳だが、大分可視化が進んできたので、これを社内のメンバーに周知しなければならない。

とは言え、説明したい内容が余りに一般的な知識なので、社内向けのクローズドなドキュメントとして書くのは勿体無いので、オープンなブログの方にまとめることにする。

(会社のテックブログに書いた方がいいのではという話はあるが、仕事っぽくなると面倒臭い……)

基本的にはOpenTelemetryの公式ドキュメントを自分なりに解釈して要点を絞る、という形で解説していくつもりなので、公式ドキュメントは一通り読んで理解したよ、という人は読む必要はない。

オブザーバビリティとは

オブザーバビリティという言葉が昨今広く言及される様になったが、そもそもオブザーバビリティとは何が出来ることを指してるのか。

それはシステムの内部がどの様に動いているかを知らなかったとしても、外形的な監視によってそのシステムが想定した形で動作していることが観測できることを指している。

そして観測内容を通じて、システムに起きている問題や未知の事象に対する疑問に解答できる様なヒントを与えることを目的としている。

昨今の成長したシステムは非常に複雑で、全てのコンポーネントでその内部の挙動を詳しく理解している人、というのは大抵の組織でほとんど存在しない。内部の挙動に関する知識に依存しない形でシステムの動作に対する疑問にある程度答えられる状況を作らなければ、とてもじゃないが運用していく人間が足りなくなってしまう。

そういう訳で、昨今オブザーバビリティという言葉が指すシステム特性が非常に重要になっている。

OpenTelemetryはこの様なオブザーバビリティ実現のために必要な構成要素を定義した仕様であり、それを実装したコンポーネントやライブラリなども含んでいる。

何を観測するのか

OpenTelemetryにおいては、こういったことを可能にするための観測対象を大きく3つに区分している。

  • トレース: ある一連の処理を示すSpanとそのSpan同士の関連性を繋げたもの。後述する。
  • メトリクス: 一定期間内のアプリケーションやインフラにおける動作情報を数値化したもの。CPU利用率とかメモリ消費とかI/O統計とかが代表的。
  • ログ: ある瞬間に起きたことを説明するタイムスタンプ付のメッセージ。アクセスログ、エラーログ、重要な処理が完了したことを示すログ、デバッグログなど。

メトリクスとログに関しては、昔から収集していることが多かったし、その二つを活用した自動アラートの仕組みを構築しているところも多いだろう。新しく理解することが多いのはトレースである。OpenTelemetryもトレースを取得するための規格策定と実装から始まっていて、今はメトリクスやログも含めて総合的に扱えるオブザーバビリティの基盤という形に発展してきた。

という訳で、メトリクスとログについては改めてここでは解説せず、トレースに絞ってより詳しく見ていく。

トレース(分散トレース)とは

ある一つの処理の開始から終了までをトラックしたものをSpanと呼ぶ。そのSpan同士を親子構造もしくはリンクによって繋げて複数の処理の関係性を表現したものがトレースである。

トレースにおいて重要なのは、複数のサービス間を跨ぐ様な処理の関係性も表現できることだ。この表現力のおかげで、あるサービスが受け取ったリクエストが更に内側のマイクロサービスにリクエストを投げてその結果をクライアントに返すといった処理の関係性を示すことができて、一連の処理の中でどういったサービスが関係していて、それぞれにどれぐらいの処理時間がかかっているかを記録することができる。処理の関係性は同期的なリクエストに限った話ではなく、RailsにおけるActiveJobを利用するケースや、SQSなどのキューを介してワーカーに処理を移譲するケースなどの、非同期処理も含まれる。

もしトレースの可視化が無ければ、実際にエンドユーザーが体験するリクエストから結果までの時間の中で、どの処理がボトルネックになっているか、どういったサービスが関係しているかを把握するのがかなり難しくなる。特に非同期処理の場合は、ある一連の処理が終端に到達するまでに実際にどれぐらいの時間がかかっているかを把握するだけでも、それなりの困難を伴う。

マイクロサービスアーキテクチャやサーバーレスコンポーネントの組み合わせでシステムを構成する際には、こういった課題は大きな問題だったが、トレースの取得が一般化したことで大分把握しやすい世界になってきた。

可視化されたトレースの例: https://opentelemetry.io/img/waterfall-trace.svg

メトリクスやログはある単体のサービスやノードで完結している情報なので、取得して可視化するまでの流れが分かり易いが、トレースにおいては複数のノード間で関係性を伝達する必要があるため、それをどうやって実現しているかを理解しておいた方が良いだろう。

ここからはトレースを実現するための構成要素をより詳しく見ていく。

Span

Spanはある一つの処理単位を表現する。具体的には以下の様な情報を持っている構造体になる。

  • Name: Spanの名前。メソッド名とかリクエストURLなんかが入る
  • Parent span ID: 親(呼び出し元)になるSpanのID。一連のトレースの視点の場合は空になり、そういったSpanはRoot Spanと呼ばれる
  • 開始時刻、終了時刻: 処理の開始と終了を示すタイムスタンプ
  • Span Context: トレース全体を示すIDや自身のSpan IDなどを保持する不変のデータ構造
  • Attributes: Spanに関係するメタデータを表現するキーバリュー構造
  • Events: Spanの中で起きた一つ一つの事象を表現する。Spanの中でも特に重要な処理が開始された記録など
  • Span Links: 明確に親子関係ではないが、関連しているSpanを示す。複数の非同期処理を待ち受けている場合など、親が一つに絞れないケースなどを表現できる
  • Span Status: そのSpanが成功に終わったかエラーに終わったかを表現する。

特に重要なのは名前とタイムスタンプで、つまりSpanとはある処理の開始時間と終了時間に名前を付けてメタデータを付与したもの、ということになる。

Context Propagation

Context

分散トレーシングにおいて、ある処理Aとある処理BがAPIの境界を跨いでるとする。例えばエンドユーザーから何らかのリスト取得APIがリクエストされたとして(A)、その処理の中でユーザーの認証処理を内部の認証基盤にリクエストして認証する処理がある(B)、という場合を考える。

この時、AとBの処理が関連ある実行単位であることを示すためのデータを保持しているオブジェクトをContextと呼ぶ。実態はキーバリューの形でデータを持っているシンプルなオブジェクトだが、満たさなければいけない仕様やContext操作のために実装しておかなければいけないAPIがOpenTelemetryの仕様に規定されている。

Propagation

Contextを複数のサービスに跨って持ち回るには、それを伝播させる仕組みが必要になる。OpenTelemetry仕様ではその実現のためのPropagator APIが規定されているが、現在仕様がちゃんと定まっていて実装が存在しているのはTextMapPropagatorのみである。

簡単に言えば、HTTPのリクエストヘッダーの様なテキストで表現できるキーバリュー構造を用いてContextオブジェクトを持ち回るためのルールを規定している。

現在、OpenTelemetryで利用するヘッダー構造の仕様としてデフォルトで利用されるのはW3C TraceContextという仕様だ。詳しく知りたい場合はリンク先のW3Cの文書を読んでもらうとして、ここで簡単に説明しておくと、現在この仕様はtrace-idとparent-idとtrace-flagsの持ち方を規定している。trace-idは一連の分散トレース全体を示すユニークなIDで、parent-idは親のSpanを示すユニークなIDで、trace-flagsはトレースがサンプリングされているかとかトレースのレベルなどを表現するいくつかのフラグを表現したものだ。

非常にざっくりした言い方をすると、HTTPのリクエストヘッダやそれに類する仕組みを用いて、ルールに則ってtrace-idとparent-idを伝播させることで、各アプリケーションが自分の処理を表現するSpanを構築する時に、どのトレースに属していて、どのSpanから呼ばれたのかを判断する、ということだ。Spanには多くの情報が付与されるが、Spanは各システムの責任で外部に送信される。トレース全体で最低限持ち回らなければいけないのは2つのIDとフラグだけで、システム間で伝播している実際の情報量はとても小さいので、システム間での情報伝達のオーバーヘッドは小さい。

この様に各Span自体は親のIDを知っているだけで独立したデータ構造で、それをどう表現するかは可視化のためのアプリケーション(Grafana, Jaeger, Zipkin, etc)やWebサービス(Datadog, Splunk, etc)の責任範囲になる。

計装と収集の分離

OpenTelemetryはこういったトレースやメトリックの概念を規定するだけでなく、それらを転送するプロトコルも一緒に規定している。これをOpenTelemetry Protocol (OTLP)と呼ぶ。

現在、OTLPはgRPCとHTTPで計測データを送るための仕様を定義している。

OTLPという仕様のおかげで、各計装ライブラリがデータを可視化基盤に送る時の実装を統一できるし、後述するCollectorを利用してデータ集約してから複数の基盤に転送するといったことが可能になっている。OTLPを利用して直接可視化基盤に対して計装データを送っても良いが、転送効率やトレースメトリクスの収集などを考慮すると、Collectorを利用してデータを送信する構成を取るのが推奨される。

OpenTelemetry Collector

OpenTelemetryのコンポーネント群として用意してくれているベンダー非依存な計装データ転送のための仕組みがOpenTelemetry Collectorだ。Collectorはこれまでに解説してきたトレースだけでなく、メトリクスやログも扱うことができる。

https://opentelemetry.io/docs/collector/img/otel-collector.svg

似たコンポーネントを挙げて表現するなら、fluentdみたいなものと言って良いかと思う。

Collectorは以下の様な構成要素を持つ。それぞれの構成要素はプラガブルになっており、必要に応じて選択して利用できる。

receiver

データを受付ける責務を受け持つ。OTLPが基本になるがCollectorはメトリクスやログも扱うし、既存のサービスからのexportも受け付けることを想定しているので、公式でメンテナンスされているだけでも大量のreceiverが存在する。

例えば、snmpやsyslogやjmxなどログやメトリクスの出力としてよく利用されるものに加えて、Kafkaを利用してデータを集約するためのkafkareceiverなども存在する。

exporter

データを外部に出力する責務を受け持つ。これもOTLPが基本でOTLPを直接受付けられる可視化基盤であればこれだけで十分だが、AWSのcloudwatch logsに出力したり、syslogに送ったり、datadogやsplunkの様な外部サービスに送ったりといったことに対応できる様になっている。

processor

データを加工したり、フィルタして除外したりする時に利用する。

SpanのAttributesに含まれる情報を利用して、送信先をコントロールしたり、不要な情報を除外したり、表記を書き換えたりなどを行う。

OpenTelemetryは仕様のバージョンがよく更新されているので、ライブラリによっては同じsemanticを表すAttributeが微妙に異なっていたりする。こういったものを正規化する目的などにも利用できる。

また、一定期間のデータをメモリ上に蓄積してまとめて処理するためのbatch processorやメモリ消費を一定に抑えるためのmemorylimiterなどもprocessorに含まれている。

connector

processorに似ているが、内部的にはexporterとreceiverとして扱える様になっている。

例えば、トレースメトリクスの収集などに利用する。トレースメトリクスはSpanの秒間リクエスト数や所要時間の統計など、トレースに関連して発生するメトリクスのことだ。connectorを利用するとSpanをbatch processorで集約し、connectorに渡すことでトレース情報から生成したメトリクスをメトリクス送信のパイプラインに繋げるといったことができる。datadogやgrafana cloud向けにそういった処理を行うconnectorが提供されている。

設定例

OpenTelemetry Collectorはyamlで設定を記述する。

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  memory_limiter:
    check_interval: 1s
    limit_percentage: ${env:MEMORY_LIMITER_LIMIT_PERCENTAGE:-75}
    spike_limit_percentage: ${env:MEMORY_LIMITER_SPIKE_LIMIT_PERCENTAGE:-20}

  batch:


exporters:
  kafka:
    brokers:
      - ${env:KAFKA_BROKER_0}:9092
      - ${env:KAFKA_BROKER_1}:9092
      - ${env:KAFKA_BROKER_2}:9092
    protocol_version: 2.1.0
    partition_traces_by_id: true
    producer:
      compression: zstd

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [kafka]

こんな感じで、OTLPで受付けたトレースデータをKafka Brokerに転送するといった処理を記述できる。

ocb (OpenTelemetry Collector Builder)

OpenTelemetryはGoで実装されたシングルバイナリのコンポーネントなので、ビルド済みのものとして配布されているものには多くのプラグインが含まれている。

もし、必要なものが既に分かっていて、より小さいバイナリが欲しい場合は、ocbというツールを使うことで必要なプラグインだけに絞ったビルドを作成することができる。 また、自分で実装した独自のプラグインを組込むためにも利用できる。

利用方法は難しくないので、リンク先を読めば使い方は大体分かるだろう。

提供されているプラグインを知るには

GitHubリポジトリを確認するのが一番早い。 Collector本体のリポジトリと、オプショナルで提供されているcontribリポジトリを参照すると何が提供されているかが分かる。

github.com

github.com

アプリケーション上での計装方法

大きく分けて自動計装と手動計装があり、自動計装でかなりの範囲がカバーできるのでそちらを利用することが多いと思うが、登場する概念を先に説明するために手動計装から解説する。

手動計装

OpenTelemetryは各言語向けにAPISDKが用意されており、メジャーな言語は大体カバーされている。

ここではRubySDKを利用して手動でSpanを作成する処理を公式ドキュメントから引用する。

まず、SDKのgemをインストールする。

gem install opentelemetry-sdk

or

source "https://rubygems.org"

gem "opentelemetry-sdk"

次にTracerを準備するコードを書く。

# If in a Rails app, this lives in config/initializers/opentelemetry.rb
require "opentelemetry/sdk"

OpenTelemetry::SDK.configure do |c|
  c.service_name = '<YOUR_SERVICE_NAME>'
end

# 'Tracer' can be used throughout your code now
MyAppTracer = OpenTelemetry.tracer_provider.tracer('<YOUR_TRACER_NAME>')

例えば、Railsのinitializerなどで初期構成を行ってTracerオブジェクトを定数に格納しておく。Tracerはスレッドセーフに実装することが仕様で決められており、大抵の場合はアプリケーションに対してシングルトンで良い。

Tracerが準備できたら計測したい場所で新しくSpanを作成する。

require "opentelemetry/sdk"

def parent_work
  MyAppTracer.in_span("parent") do |span|
    # do some work that the 'parent' span tracks!

    child_work

    # do some more work afterwards
  end
end

def child_work
  MyAppTracer.in_span("child") do |span|
    # do some work that the 'child' span tracks!
  end
end

Rubyであれば、do-endのブロックをSpan計測区間として直感的に表現できる。OpenTelemetryのAPIは上記の様にあるSpan計測区間の中でin_spanメソッドを呼んでSpanを作成すると暗黙的に親を認識して入れ子のSpanにしてくれる。

現在のSpanを取得したい場合は以下の様にすれば良い。

current_span = OpenTelemetry::Trace.current_span

current_span.add_attributes({
  "my.cool.attribute" => "a value",
  "my.first.name" => "Oscar"
})

取得したSpanに対してattributesを追加することもできる。

Spanの情報はContextオブジェクト保持されていて、Rubyの実装ではContextオブジェクトはスレッドローカルストレージに確保される様になっている。

      def stack
        Thread.current[STACK_KEY] ||= []
      end

なのでクラスメソッドを呼ぶと現在のスレッドの最新のContextを経由してSpanを取得することができる。

基本的にはこれだけ覚えておけばトレースを記録することができる様になるが、もう少し登場概念について説明しておく。

TracerProvider

Tracerを生成するためのファクトリで、アプリケーション単位で構成される。後述するResourceやアプリケーションからcollectorや外部サービスにデータを送信するためのexporterの情報を持っている。

Tracer

TracerProviderから生成される実際にSpanを作成するためのオブジェクト。

Span

Tracerが作成するアプリケーションの処理単位ごとの情報を保持するオブジェクト。詳しくは上記を参照。

Resource

Attributeと似ていて分かりにくいのだが、テレメトリデータを生成するエンティティを表す属性値を示す。

どういうことかと言えば、インフラ情報やプロセス名などアプリケーションの単位で不変なものを表現する。例えば以下の様なものが該当する。

  • コンテナID
  • コンテナのイメージ名
  • OS情報
  • デプロイ先の環境名(production, staging, etc)
  • プロセス名と引数
  • クラウドベンダー特有の情報
    • ECSのタスク名やcluster arn
    • GCPのCloudRunの実行ID

これらはSpanに付随する情報としても利用されるが、Spanごとに変化することは無いものとして扱われるので別の概念が割当てられている。

自動計装

OpenTelemetryの文脈ではZero-code Instrumentationとも表現される。

RubyはOpenTelemetryでの定義上はZero-code Instrumentationには含まれていないのだが、実際ほとんど同じ様なものなので、この記事では自動計装とまとめてしまう。

Rubyでは以下の様にすることで自動計装が有効になる。

# Gemfile
gem "opentelemetry-instrumentation-all"
# config/initializers/opentelemetry.rb
require 'opentelemetry/sdk'
require 'opentelemetry/instrumentation/all'
OpenTelemetry::SDK.configure do |c|
  c.service_name = '<YOUR SERVICE>'
  c.use_all() # enables all instrumentation!
end

この様にすることでopentelemetry-instrumentation-allに含まれる全ての計装処理が有効になる。

個別に利用する場合は以下の様になる。

OpenTelemetry::SDK.configure do |c|
  c.use 'OpenTelemetry::Instrumentation::Rack'
end

opentelemetry-instrumentation-allに含まれる計装を知りたい時はopentelemetry-ruby-contribリポジトリを参照する。

github.com

ここを見ると、HTTPのリクエストやrack、railsのエントリポイント、Active Recordの呼び出しsidekiqの呼び出しなどで自動的に計装してくれることが分かる。

Rubyではどうやって自動計装を実現しているのか

ここでは、active_recordのinstrumentation実装例を見てみよう。

# lib/opentelemetry/instrumentation/active_record/instrumentation.rb

module OpenTelemetry
  module Instrumentation
    module ActiveRecord
      # The Instrumentation class contains logic to detect and install the ActiveRecord instrumentation
      class Instrumentation < OpenTelemetry::Instrumentation::Base
        MINIMUM_VERSION = Gem::Version.new('7')

        install do |_config|
          require_dependencies
          patch_activerecord
        end

        present do
          defined?(::ActiveRecord)
        end

        compatible do
          gem_version >= MINIMUM_VERSION
        end

        private

        def gem_version
          ::ActiveRecord.version
        end

        def require_dependencies
          require 'active_support/lazy_load_hooks'
          require_relative 'patches/querying'
          require_relative 'patches/persistence'
          require_relative 'patches/persistence_class_methods'
          require_relative 'patches/persistence_insert_class_methods'
          require_relative 'patches/transactions_class_methods'
          require_relative 'patches/validations'
          require_relative 'patches/relation_persistence'
        end

        def patch_activerecord
          ::ActiveSupport.on_load(:active_record) do
            # Modules to prepend to ActiveRecord::Base are grouped by the source
            # module that they are defined in as they are included into ActiveRecord::Base
            # Example: Patches::PersistenceClassMethods refers to https://github.com/rails/rails/blob/v7.0.0/activerecord/lib/active_record/persistence.rb#L10
            #   which is included into ActiveRecord::Base in https://github.com/rails/rails/blob/914caca2d31bd753f47f9168f2a375921d9e91cc/activerecord/lib/active_record/base.rb#L283
            ::ActiveRecord::Base.prepend(Patches::Querying)
            ::ActiveRecord::Base.prepend(Patches::Persistence)
            ::ActiveRecord::Base.prepend(Patches::PersistenceClassMethods)
            ::ActiveRecord::Base.prepend(Patches::PersistenceInsertClassMethods)
            ::ActiveRecord::Base.prepend(Patches::TransactionsClassMethods)
            ::ActiveRecord::Base.prepend(Patches::Validations)

            ::ActiveRecord::Relation.prepend(Patches::RelationPersistence)
          end
        end
      end
    end
  end
end
# lib/opentelemetry/instrumentation/active_record/patches/querying.rb

module OpenTelemetry
  module Instrumentation
    module ActiveRecord
      module Patches
        # Module to prepend to ActiveRecord::Base for instrumentation
        module Querying
          def self.prepended(base)
            class << base
              prepend ClassMethods
            end
          end

          # Contains ActiveRecord::Querying to be patched
          module ClassMethods
            method_name = ::ActiveRecord.version >= Gem::Version.new('7.0.0') ? :_query_by_sql : :find_by_sql

            define_method(method_name) do |*args, **kwargs, &block|
              tracer.in_span("#{self} query") do
                super(*args, **kwargs, &block)
              end
            end

            private

            def tracer
              ActiveRecord::Instrumentation.instance.tracer
            end
          end
        end
      end
    end
  end
end

DSL的な実装になっているが、基本的にはライブラリの必要なクラスに対してモジュールをprependすることでSpan作成の処理を挟み込んでいる。 そして、その中でTracer#in_spanを呼んでspanを作成するというシンプルな実装になっている。

もし自分が利用しているライブラリ向けのinstrumentationが提供されていない場合は、この実装を参考にしてmodule prependする様なパッチを当てることで、アプリケーションエンジニアが意識しない形でトレースを取得することが出来るだろう。

Tracerの設定方法

トレースの作成自体は上記の様なAPIを利用して行えるが、アプリケーションに対して取得したデータをどこに送るのかや、共通で追加したいメタデータなどを設定しなければならない。

プログラム上で指定することは可能だが、昨今のコンフィグの入力方法としては環境変数を利用する方法を理解しておくのが良いだろう。このやり方なら言語非依存なのでどの言語で書かれたアプリケーションでも同じ様に設定できる。

設定に利用できる環境変数は以下のリンクから一覧できる。

ここで示されている環境変数を指定することで、計測したデータの送信先やトレースをサンプリングする割合などを設定できる。大量にアクセスがあるシステムだとトレースに関するデータを全て収集していると物凄いデータ量になってしまうので、サンプリングの設定は非常に大事な要素の一つだ。

トレース取得の結果得られるもの

分散トレーシングが可能になると、上記の画像の様に複数のサービスを跨いだ処理のフレームグラフが得られるだけではない。

各サービス間の関係性が分かるので、サービスやミドルウェアへの依存関係やリクエスト量などを可視化することもできる。

特にマイクロサービスアーキテクチャを採用していると、このサービスが何のサービスから入力を受けて、どのサービスに向けて出力しているのか、このサービスはRDBを利用しているのか否かといった情報がすぐに分からなくなってしまう。

コンポーネント同士で担当チームが違うことも有り得るので、自分達の扱うコンポーネントが隣接しているものが何かと、それを管理しているチームがどこかが簡単に把握できることはとても重要だ。

こういった需要に応えられる様に、多くの可視化基盤ではService GraphやService Mapと呼ばれる機能が提供されている。例えがGrafanaだと以下の画像の様に可視化される。

こういったビューがあると、自分がフォーカスしているサービスに関係しているサービスが何なのかを即座に把握できる様になるし、システムの全体像を描くシステム構成図も簡易的なものであれば自動的に作成することができる。

ある程度複雑になってしまったシステムでは、何もかもモノリスで済む様に構成するのはかなり難しいので、こういった仕組みは開発の大きな助けになるだろう。

より進んだオブザーバビリティへ

この記事ではOpenTelemetryとその中でも特にトレースを中心にした解説をしてきたが、最初に述べた様にオブザーバビリティはトレースだけに留まるものではなく、システムの動作を外から観測可能にして開発の助けになる情報が得られる状態を目指すものだ。 その可視化対象はメトリクス、ログ、運用におけるプレイブックの管理、プロファイラの計測結果、担当チームへのコンタクト情報、など多岐に渡る。

最近の可視化基盤では、これらの情報を関連付けて統合的なビューとして見られる様な機能が提供されていることも多い。

アプリケーションを開発する時に、こういった可視化しておくと後々便利になる情報を意識しておくと、未来のスムーズな開発体験に繋げることができるだろう。仮にインフラエンジニアやSREが社内に居るとしても、アプリケーション開発者自身が健全な運用に必要な知識を得ておくことはDevOpsの精神としても重要なことだと思う。

卑近な言い方をすると、ちゃんと役に立つ情報を収集してまとめておけば、他人に何度もシステム構成を説明する手間も省けるし、説明資料をメンテする工数も削減できるし、多くの人間がボトルネック調査をしやすくなる、という感じで仕事が楽になるので、そのために役に立つ情報はちゃんと学んでおこう、ということだ。

最近は色々と便利なものが出来てるので、ちゃんと活用してしっかり楽をしつつより良い開発を目指していこう。

最近のLinuxのHDR事情とゲーム画面をキャプチャする方法について

去年末にLinuxHDR表示を有効にしてゲームをする方法について記事を書いたんですが、年明けでまた状況が変わってきたので追加の記事を書いておこうと思う。

HyprlandのHDR対応

元々、KDE PlazmaデスクトップだけHDR表示に対応しているという状況だったのが、今年になってからリリースされたHyprland-0.47.0からHDR表示に対応した。🎉

という訳で、experimentalな機能ながらWaylandでタイル型のウインドウマネージャーを使いたかった自分の様な人間にも道が開けてきた。

では、実際にはどうやって利用するのか。

Hyprland-0.47.0では以下の様な設定を追加する。

experimental {
  wide_color_gamut = true
  xx_color_management_v4 = true
  # debug的な用途でディスプレイ表示を強制的にHDRモードに切り替える
  # hdr = true
}

monitor=DP-1, 3840x2160@120, 0x0, 1, bitdepth, 10

また、Hyprlandのgitのmasterでは既に設定方法が変わっており、その場合は以下の様にする。次のバージョンではこの設定になるだろう。(これも変わるかもしれないが)

render {
  cm_fs_passthrough = true # defaultでtrueなのでHDRを利用するだけならモニターを10bit表示にしておくだけで良い。
}

monitor=DP-1, 3840x2160@120, 0x0, 1, bitdepth, 10

# monitor=DP-1, 3840x2160@120, 0x0, 1, bitdepth, 10, cm, hdr # 強制的にHDRモードに切り替えるならこう

そして、モニターの色深度を10bitにしておく。いわゆる10億色表示のことで、通常は8bitカラーなので1670万色表示。 ちゃんと設定できていると、amdgpuなら以下の様なコマンドで確認できる。

sudo cat /sys/kernel/debug/dri/0/crtc-0/amdgpu_current_bpc
Current: 10

(ただ、amdgpuだとここにバグがあったのでHyprlandのレンダリングライブラリをgit版に更新しないと駄目かもしれない)

こんな感じで出力できていれば概ねOK。

ここからHDRを有効にして画面表示をするなら、frog-color-management-v1 か xx-color-management-v4 に対応したクライアントが必要になる。

具体的なツールとしては、以前の記事で紹介した gamescope と VK_hdr_layer がある。

gamescopeはDXVK_HDR=1 gamescope -f --hdr-enabled -- %command%の様な感じでゲームを起動すれば、中のゲームがHDRに対応していればディスプレイがHDRモードに切り替わる。強制的にそうするオプションもある(詳しくはhelp参照)。DXVK_HDR=1にしておかないとゲーム側がHDRに対応していることを認識できない。

HyprlandがHDR対応する前は、コンソールから直接起動するembedded modeでしかHDR切り替えは動作しなかったが、Hyprlandの対応のおかげで通常のwaylandデスクトップからでも切り替えられる様になっている。🎉

mpvなどを利用して動画を見る場合はVK_hdr_layerを使う。vulkanの表示レイヤーにHDR表示のためのcolor management切り替え処理を行うレイヤーを追加するものだ。

これをインストールした上でENABLE_HDR_WSI=1 mpv --vo=gpu-next --target-colorspace-hint --gpu-api=vulkan --gpu-context=waylandvk "filename"の様にしてmpvを起動しフルスクリーンにすると画面がHDR表示モードに切り替わるはず。

target-colorspace-hintはインプットの動画ファイルのカラースペースをディスプレイ表示のカラースペースとしてそのまま利用する設定だ。基本的にHDR動画は色深度が10bitでBT.2020という色域でマスタリングされているが、それを画面表示にそのまま利用することになる。 この設定をしない場合は、BT.709と8bit colorで表示される様にmpvが調整して、その上でtone-mappingという機能で色域を補正していたのでそこまでおかしな表示にはなってなかったはずだが、tone-mappingをoffにするとめちゃくちゃ彩度が高く見えるはず。ちなみに、target-colorspace-hintを有効にした上でディスプレイの表示がHDRモードになっていない場合は、逆にディスプレイ側の表示色域が足りてないのでHDR動画の彩度がめちゃくちゃ低く見えると思う。

mpvHDR表示をする際には、追加で以下の様な設定を入れても良い。

[hdr]
target-colorspace-hint=yes
target-prim=bt.2020
target-trc=pq
target-peak=800
target-contrast=1000000
fullscreen=yes
hdr-compute-peak=yes
tone-mapping=bt.2446a
inverse-tone-mapping=yes
hdr-contrast-recovery=0.2

特に影響が大きいのはtarget-peakとinverse-tone-mappingの設定になる。target-peakはディスプレイの明るさのターゲットを指定するもので、自分の感覚では使ってるディスプレイの最大輝度よりちょっと大きいぐらいを指定しておくのが良さそうに思う。inverse-tone-mappingは名前の通り逆向きのtone-mappingでSDR画質(つまり通常の動画)の色域をHDRに拡張する。実際、そう都合よく綺麗になる訳ではないが、HDR表示モードでSDRの動画を見るとこれまた彩度がめちゃくちゃ高くなってしまってヤバイ表示になるのを何とかしてくれる。

基本的にこれらの機能はフルスクリーンでしか機能しない。そもそもディスプレイが画面全体をHDR表示モードにするかSDR表示モードにするかしかコントロールできないので、画面の一部だけHDRとかを上手いことやるのは難しいだろう。

という訳で、Linuxでも設定さえちゃんとやってしまえば普通にHDR表示が出来る、ぐらいまでは到達した様に思う。まあ、そもそもコンテンツが余り無いのだが……。配信作品とかはブラウザがそもそもそういう表示に対応してないので当分無理だろうし……。現状はLG辺りが出してるデモムービーぐらいしか無い。やはり現状で一番有用なのはPCゲームである。

HDR表示しているゲーム画面をキャプチャする

Linuxでゲーム画面をキャプチャしたい場合は、obsとobs-vkcaptureというプラグインを使うと良い。obs-vkcaptureはデスクトップ表示からキャプチャするのではなくゲーム画面をレンダリングするvulkanのレイヤーにキャプチャ層を追加して直接obsに流すことで低遅延かつ高画質でキャプチャできる様にしてくれるプラグインだ。

obs-vkcaptureはHDR表示に対応していてHDR表示されている場合は、その色域をちゃんとobsまで流してくれる。

obs側では詳細設定の項目から、カラーフォーマットをP010、色空間をRec.2100 (PQ) に設定し、HEVCからAV1を使ってキャプチャする様に設定してからキャプチャを開始すれば、HDR出力をそのままHDR動画としてキャプチャできる。

現実的に今それなりの解像度と60fpsをリアルタイムでキャプチャ可能なのはHEVCのハードウェアエンコードぐらいだと思う。AV1は自分が持ってるGPUが対応してないのでハードウェアエンコードでどれぐらいパフォーマンス出るのか分からんが、HEVCより遅いという話は聞く。今のところパテントフリーなので応援はしているが。

そして、amdgpuのVAAPIを利用したハードウェアエンコードはぶっちゃけ画質が余り良くないので、そこそこの画質で出力しようとすると普通に30Mbpsぐらい必要になる。録画なら良いが配信だと結構良い回線が必要である。

という訳で、最近色々と状況がアップデートされたLinuxでのHDR事情はこんな感じだ。

LinuxでDisplay HDRを有効にしてゲームをプレイする方法

PS5やWindows PCの様にLinuxHDR対応コンテンツを楽しめるのかですが、結論から言えば、一応可能です。 ただ、そんなにお手軽かというとそうではないし、それなりに制約があります。また、自分の目ではちゃんとなっている様に見えますが本当に想定された色が出てるのかというと良く分からんという感じではあります。

という訳で、多少問題はあるかもしれませんが可能は可能なので、その実現方法について紹介します。

LinuxHDRサポート状況

参考: HDR monitor support - ArchWiki

現状、デスクトップ環境レベルでHDRをサポートしているのはKDE Plasmaがwaylandセッションで実験的にサポートしているだけです。 X.orgにおいてはサポートされる見込みは恐らくありません。

GNOMEデスクトップはサポートに向けて頑張っている最中の様です。

DRMレベルでHDR metadataを扱うことは可能です。一般的なuserspaceのクライアントで利用可能なものはありませんが、特定のソフトウェアで対応しているものがあります。

自分が利用しているHyprlandというwaylandのWMは実装が進められている様です。今後に期待したい。(https://github.com/hyprwm/aquamarine/pull/112)

という訳で、現時点でまともにHDRコンテンツを扱う方法は自分が知ってる限りではgamescopeを利用する方法だけです。

gamescopeについて

gamescopeはValveSoftwareがSteamOS向けに開発したWindow Managerです。 gamescopeは単体のGUIアプリを起動するのに特化したWindow Managerでゲームフレームの描画に最適化されています。そのため、通常のデスクトップ環境で起動するよりもゲームフレームの描画がスムーズになります。

gamescopeにはembeddedモードとnestedモードという二種類のモードがあり、通常のデスクトップ環境で起動するとnestedモードになります。Xwaylandのサンドボックスデスクトップを起動して既存のデスクトップ環境と干渉することを避け、任意の解像度とリフレッシュレートを偽装することができます。

embeddedモードではDRM/KMSを利用してXwaylandを起動し、この上でゲームを起動することで余計なフレームコピーを回避して描画を高速化しています。

このembeddedモードでDRM/KMSを利用する場合において、HDRを有効化できます。

Steamをプレイする場合の実行手順

最初にgamescopeのhelpを紹介しておきます。

 gamescope --help                                                                 
[gamescope] [Info]  console: gamescope version  (gcc 14.2.1)
usage: gamescope [options...] -- [command...]

Options:
  --help                         show help message
  -W, --output-width             output width
  -H, --output-height            output height
  -w, --nested-width             game width
  -h, --nested-height            game height
  -r, --nested-refresh           game refresh rate (frames per second)
  -m, --max-scale                maximum scale factor
  -S, --scaler                   upscaler type (auto, integer, fit, fill, stretch)
  -F, --filter                   upscaler filter (linear, nearest, fsr, nis, pixel)
                                     fsr => AMD FidelityFX™ Super Resolution 1.0
                                     nis => NVIDIA Image Scaling v1.0.3
  --sharpness, --fsr-sharpness   upscaler sharpness from 0 (max) to 20 (min)
  --expose-wayland               support wayland clients using xdg-shell
  -s, --mouse-sensitivity        multiply mouse movement by given decimal number
  --backend                      select rendering backend
                                     auto => autodetect (default)
                                     drm => use DRM backend (standalone display session)
                                     sdl => use SDL backend
                                     headless => use headless backend (no window, no DRM output)
                                     wayland => use Wayland backend
  --cursor                       path to default cursor image
  -R, --ready-fd                 notify FD when ready
  --rt                           Use realtime scheduling
  -T, --stats-path               write statistics to path
  -C, --hide-cursor-delay        hide cursor image after delay
  -e, --steam                    enable Steam integration
  --xwayland-count               create N xwayland servers
  --prefer-vk-device             prefer Vulkan device for compositing (ex: 1002:7300)
  --force-orientation            rotate the internal display (left, right, normal, upsidedown)
  --force-windows-fullscreen     force windows inside of gamescope to be the size of the nested display (fullscreen)
  --cursor-scale-height          if specified, sets a base output height to linearly scale the cursor against.
  --hdr-enabled                  enable HDR output (needs Gamescope WSI layer enabled for support from clients)
                                 If this is not set, and there is a HDR client, it will be tonemapped SDR.
  --sdr-gamut-wideness           Set the 'wideness' of the gamut for SDR comment. 0 - 1.
  --hdr-sdr-content-nits         set the luminance of SDR content in nits. Default: 400 nits.
  --hdr-itm-enable               enable SDR->HDR inverse tone mapping. only works for SDR input.
  --hdr-itm-sdr-nits             set the luminance of SDR content in nits used as the input for the inverse tone mapping process.
                                 Default: 100 nits, Max: 1000 nits
  --hdr-itm-target-nits          set the target luminace of the inverse tone mapping process.
                                 Default: 1000 nits, Max: 10000 nits
  --framerate-limit              Set a simple framerate limit. Used as a divisor of the refresh rate, rounds down eg 60 / 59 -> 60fps, 60 / 25 -> 30fps. Default: 0, disabled.
  --mangoapp                     Launch with the mangoapp (mangohud) performance overlay enabled. You should use this instead of using mangohud on the game or gamescope.

Nested mode options:
  -o, --nested-unfocused-refresh game refresh rate when unfocused
  -b, --borderless               make the window borderless
  -f, --fullscreen               make the window fullscreen
  -g, --grab                     grab the keyboard
  --force-grab-cursor            always use relative mouse mode instead of flipping dependent on cursor visibility.
  --display-index                forces gamescope to use a specific display in nested mode.
Embedded mode options:
  -O, --prefer-output            list of connectors in order of preference
  --default-touch-mode           0: hover, 1: left, 2: right, 3: middle, 4: passthrough
  --generate-drm-mode            DRM mode generation algorithm (cvt, fixed)
  --immediate-flips              Enable immediate flips, may result in tearing
  --adaptive-sync                Enable adaptive sync if available (variable rate refresh)

Debug options:
  --disable-layers               disable libliftoff (hardware planes)
  --debug-layers                 debug libliftoff
  --debug-focus                  debug XWM focus
  --synchronous-x11              force X11 connection synchronization
  --debug-hud                    paint HUD with debug info
  --debug-events                 debug X11 events
  --force-composition            disable direct scan-out
  --composite-debug              draw frame markers on alternating corners of the screen when compositing
  --disable-color-management     disable color management
  --disable-xres                 disable XRes for PID lookup
  --hdr-debug-force-support      forces support for HDR, etc even if the display doesn't support it. HDR clients will be outputted as SDR still in that case.
  --hdr-debug-force-output       forces support and output to HDR10 PQ even if the output does not support it (will look very wrong if it doesn't)
  --hdr-debug-heatmap            displays a heatmap-style debug view of HDR luminence across the scene in nits.
Reshade shader options:
  --reshade-effect               sets the name of a reshade shader to use in either /usr/share/gamescope/reshade/Shaders or ~/.local/share/gamescope/reshade/Shaders
  --reshade-technique-idx        sets technique idx to use from the reshade effect

Steam Deck options:
  --mura-map                     Set the mura compensation map to use for the display. Takes in a path to the mura map.

Keyboard shortcuts:
  Super + F                      toggle fullscreen
  Super + N                      toggle nearest neighbour filtering
  Super + U                      toggle FSR upscaling
  Super + Y                      toggle NIS upscaling
  Super + I                      increase FSR sharpness by 1
  Super + O                      decrease FSR sharpness by 1
  Super + S                      take a screenshot
  Super + G                      toggle keyboard grab

オプションの中に--hdr-enabledなどがあることが分かりますが、この辺を利用します。

embeddedモードで起動しなければならないので、まずCTRL+ALT+F2(〜F6)ホットキーを使って、デスクトップ環境が起動していないセッションに切り替えます。どの番号が空いてるかは環境に依りますが、大抵F1かF7でデスクトップが起動していると思うのでF2は空いてるでしょう。

コンソール上で以下のコマンドを使ってsteamを起動します。

ENABLE_GAMESCOPE_WSI=1 ENABLE_HDR_WSI=1 DXVK_HDR=1 gamescope -e --xwayland-count 2 -o "DP-1,*" --hdr-enabled --hdr-itm-enable -- steam -gamepadui

DP-1の部分は自身が接続しているディスプレイのポートに合わせて読み替えてください。

WSI関連の環境変数はgamescopeのHDR対応の有効化とvulkan APIHDR対応の有効化に必要な様です。 DXVK_HDR=1は、ProtonやWineで起動するゲームアプリケーションのHDR対応を有効化するために必要で、VulkanでエミュレートしているDirectXAPIに対してHDR機能の有効化を指示する環境変数です。この環境変数を付けて起動すれば、対応しているゲームならゲーム内の設定でHDRの有効・無効が切り替えられる様になるはずです。

上記のコマンドでsteamが起動すると、この時点でディスプレイのHDRモードが有効になるはずです。

この状態で、steamのゲームを起動し、DXVK_HDR環境変数が正しく渡っていれば、HDR対応のゲームをHDRモードでプレイできるはずです。もしゲーム内まで環境変数が伝播していなければ、steamtinkerlaunchなどの起動ラッパーを使うか、steam上の設定で実行コマンドに環境変数を付与すれば対応できるでしょう。

自分の環境ではサイバーパンク2077とDMC5において、HDRモードでプレイすることができました。

DRMで直接steamを起動していることによる制約

この状態だとディスプレイが1枚しか使えません。ゲームをプレイしつつサブディスプレイでWebブラウザを表示するなどは不可能です。

CTRL+ALT+F1でデスクトップ環境が起動しているセッションに戻ることで通常のデスクトップアプリを操作することはできます。この時、ディスプレイのHDRモードが自動でオフになったりはしないので、やたらビビッドな色で表示されることになりますが、攻略サイトを見たりすることは出来ると思います。

steamを落としてgamescopeプロセスを終了したらディスプレイのHDRモードが解除されるはずです。

HDR動画を試聴する

世の中にそんなにHDR対応の動画コンテンツは無いんですが、mpvとgamescopeを組み合わせることでHDRモードで動画を再生することが出来ます。

mpvのconfigに以下の内容を追加します。

[hdr]
target-colorspace-hint=yes
inverse-tone-mapping=yes
target-prim=bt.2020
target-trc=pq
target-peak=600

大事なのはtarget-colorspace-hintとinverse-tone-mappingです。

設定を追加したら、以下の様にmpvを起動します。

ENABLE_GAMESCOPE_WSI=1 ENABLE_HDR_WSI=1 gamescope -e --xwayland-count 2 -O "DP-1,*" --hdr-enabled -- mpv --profile=hdr <movie file>

これでディスプレイがHDRモードに切り替わった状態でmpvが起動し、tone-mappingによるHDR->SDR変換ではなくHDRの色情報のまま動画が表示できるはずです。

多分、色的にも正しいと思うのですが、そもそも理想的な色状態に確信が無いので、ちょっと何とも言えないところはありますが……。

という訳で、gamescopeを使って頑張れば現状のLinuxでもHDR対応コンテンツを楽しむことは出来ると思います。

この記事が誰かの参考になれば幸いです。

大阪Ruby会議04でRustでRubyGemを書く話をしてきました

8/24に開催された大阪Ruby会議04に参加してました。

自分は地元が関西なのもあって相性が良いのか、大阪で開かれた地域Ruby会議ではよく登壇する機会があって、今回もそうなることができて良かったなと思っています。

今回の大阪Ruby会議はチーフオーガナイザーであるydahさんの趣味が出てる感じがして、めちゃくちゃ楽しいイベントでした。なんというかめっちゃRubyKaigiそのものっぽかった。

実は少し前に実家の母が倒れて入院したりといった事件があったので、お見舞のために奈良と大阪を往復したり、ホテルが1日ごとに変わって荷物がやたら多くなったりと結構バタバタしてしまって体力は消耗したんですが、結果的には無事に楽しむことができて安心しました。

セッション内容について

今回の両キーノートスピーカーである金子さんとはすみさんは、この二人なら信頼感しか無いなと思っていたのですが、期待以上に素晴らしい発表でした。

色々バタバタしてたのもあって金子さんのブログは事前に読めていなかったのですが、エディタとLSP周りは色々調べたことがあって、その延長でConcrete Syntax Tree (Lossless Syntax Tree)とSwiftやC#の関係についても少し触れたことがありました。前日の飲み会でその辺りの触りを聞くことができたので、色々心構えができて、楽しく聞くことができました。

はすみさんのキーノートはMatzスタイルにkakutaniさんが話しているかの様な良い話が組み合わさって、いかにもキーノートって感じで素晴らしかった。良い意味で「役に立たない」話って楽しいですよね、っていうのはRubyKaigiを始めとする技術者コミュニティではしばしば感じられる価値観で、自分もとても好きな考え方の一つです。自分もそういう風にネタを提供できたら良いなあと思いました。

キーノート以外で、今回めっちゃ良いなと思ったのはkoicさんのminifyrbの話です。Rubyをminifyするというのは現時点でそんなに実用性が無いことなんですが、そこからRubyの意味論の話に繋がって、そしてその上で自分がコミッタをやっているRubocopでソースコードを直すという完璧なマッチポンプに繋がっていました。しかもそのおかげでRubocopのエッジケースのバグが見つかって修正に繋がっている。楽しそうだから遊んでみて、結果OSSがちょっと便利になっているというのは本当に良い話だなと思いました。

自分の登壇について

発表資料はこちらです。 Rustで作るtree-sitterパーサーのRubyバインディング - Speaker Deck

作ったもの。 github.com

パーフェクトRubyという本を書いた時にC拡張の話を書いたことがあって、今だとRustでも書けるしやってみたいなあと思っただけというやつですが、色々と勉強になることがありました。

今回、細かいRustの解説をやっている時間がなかったので、Rustの知識がかなり前提になってしまう発表になってしまったのが反省点の一つです。なので、ここで少し補足を書いておきたいと思います。

RustでRubyGemを書くことの特徴の一つとして、メモリモデルの理解が必要になるという点が上げられます。

RustはGCが無い言語ですが、所有権システムとライフタイムによって必要なくなったデータを利用者が余り意識しなくても開放してくれます。また、基本的にデータはスタックに置くという考え方があると思っています。

スタックというのは雑に言えば、関数呼び出しをした時にその関数のために割り当てられる領域です。関数呼び出しから更に関数呼び出しを行うと、領域が積み上げられて終わったら結果を取り出して開放するという動きから積み重ねという意味でスタックと呼びます。

スタックは関数呼び出しが終わったらすぐに開放されます。例えばRubyでメソッドの中で定義したローカル変数はメソッドを抜けたら使えなくなりますが、Rustではもうちょっと広い領域で考える必要があります。

Rustの関数呼び出しの引数として所有権を持った型を受け取る様になっていると、その関数の呼び出しが終わった時に返り値としてその値を返さない限りはスタック開放時に一緒に受け取った引数も開放されて消滅します。所有権を引き渡すということは、基本的にはもう元の箇所で使わないという意志表示であり、関数の返り値になっていないということは関数の呼び出し元でも必要ないという意志表示です。なのでRustは必要なくなったデータを開放します。

そうなると困るデータを扱うために借用という概念を利用します。借用を利用すれば、データの所有権は元のスタックに残したまま、呼び出し先で値を参照したり、参照先のデータを書き換えたりできる様になります。

Rustで普通にコードを書いている時は、借用型やスタックに置いてるデータを意識することでデータの生存期間をコントロールできますが、RubyGemを書く時にいくつかの制約があってコントロールが難しくなります。

一番問題なのは、Rubyからのメソッド呼び出しの結果、いきなりRustの世界に入ってきて(その様に見える)その関数を抜けたらRustが理解しているスタックが消滅することです。

また、RustがアロケートしたデータをRubyオブジェクトとして利用したい時は、そのデータをRust側でヒープに置いてはいけないという制約もあります。これはRustからヒープに置いたデータをRuby側で認識できないので、そうしてしまうとメモリリークに繋がってしまうからです。これはつまり、Rust上でコードを書く時、Rubyのオブジェクトとして扱いたいデータをVec型などのヒープにデータ領域を確保する型に入れてはいけない、ということを意味します。これは結構面倒臭い。

という訳で、基本的にRustでアロケートした構造体などをRubyオブジェクトとして利用するためには、参照を受け取って所有権のある型を返すか、Rubyの管理しているヒープにオブジェクトとして存在していることが分かっているデータの参照を返す必要が出てきます。この辺のメモリの扱い方の関係でRubyGemとして利用できるメソッドシグネチャにはいくつかのルールがあります。その辺りのことはスライドにも書いてあるので一緒に参照していただけると良いかなと思います。

メモリの扱いに関する別の要素としては、RubyGCで不必要だと判断されたらRust側のデータも開放される様にライブラリが面倒を見てくれる様になってますが、Rustの世界での構造体同士の関係をRubyGCでに追いかけることはできないため、ちゃんと追いかけられる様にRubyオブジェクトとしての構成をコントロールするか(インスタンス変数に参照を割り当てておくとか)、RcやArc型などを使ってRust側でメモリが開放されない様な工夫をしないと、予期せぬ箇所でSEGVに繋がることがある、というハマりポイントがあります。

これは本質的にはRustに限った話ではなく、CでRubyGemを書く時もRubyGCが動く時に、実際にはどこを開放して良い領域なのかは考えておく必要があります。ただ、Cだとマニュアルで開放しなければいけないところを、Rustで書くとライブラリと所有権システムがかなり面倒を見てくれるので、楽な反面意識から抜けてしまうという印象がありました。 Rust自体はダングリングポインタなどが発生しにくい様に設計された言語ですが、RubyGCと組み合わさると普通にそういった事象が発生してしまうので、Rustを使っているからといって、それだけで安全になる訳ではないということですね。

何にせよ、GCはやっぱり難しいなと改めて認識できたのは今回の収穫と言えるかなと思います。

最後に、イベント後の懇親会でがちゃぴん先生に、「結局Rustで書くのってオススメなの?それがよく分からなかった」と言われて、確かにちゃんと書いてなかったなと思ったので、自分なりの結論を書いておこうと思います。

あくまで個人的な感想ですが、ネイティブ拡張を書きたいと思った時に、CとRustの習熟レベルが同じ程度ならRust使った方が多分楽だしオススメです。色々な便利ライブラリもCargoで管理してくれて使い易いし、文字列や配列状のデータの様によく扱う割にCだと面倒なことも上手くカバーしてくれますし、メモリ管理もCより確実に楽で安全です。一方でCなら何度も書いてきたし何とでもなるという人が無理してRust使っても、凄く楽になるかというとそうでもない気はします。自分はCもRustも大したことないので、勉強すること自体は色々あったけどRustの方が安全だし書き易いんじゃないかという感想でした。

まあそもそもの話ですが、CやRustでRubyGemを書くということ自体、そんなに頻繁に発生することではありません。今回題材にした様なCで提供されるライブラリのバインディングを書いたり、とにかくパフォーマンスを稼ぎたい時に必要に迫られたりといったケースでないなら、余り必要のない技術です。そういった意味では今回会場で発表を聞いてくれた皆さんの多くにとって、すぐには「役に立たない」話と言えますね。自分にとってさえ、明日仕事で使える様な話かというと全然そんな話ではありません。

しかし、いつ自分の様にやたら大量のデータをRubyで扱う様な仕事に就くか分からんもんです。自分も昔は全然想像してなかったことですが、そういう仕事をしてると、ここで何とかパフォーマンスを稼ぎたい、という状況が発生することがあります。そういった時に使える選択肢を増やしておくことは、すぐには役に立たなくてもいつか役に立つかもしれないなと思っています。

何年後かにそういう状況になったり、ふと思い出してRustの勉強をしてみたくなった時に、この記事と発表資料が力になると幸いです。

今回、登壇機会がいただけて、自分の学習にも色々繋げることができました。大阪Ruby会議04を開催してくれた皆様、一緒に参加してくれた皆様に、改めてお礼を申し上げます。

楽しいイベントをありがとうございました。またRubyコミュニティでお会いしましょう!

RubyKaigi 2024に参加できて本当に良かった

RubyKaigi 2024に参加してきました。

今回参加までに紆余曲折あったので、一時は参加を諦めていたんですが、何とか無事参加することが出来ました。

2011年に初参加して以来休まず参加していたので、ついに連続参加が途絶えるのかと思ってましたが、無事連続参加を達成できて嬉しい限りです。

今回はそういう事情もあってか、コミュニティとの繋がりを強く感じることができたRubyKaigiでした。

色々思いが溢れてしまって、技術的に楽しかったこと、自分が嬉しかったこと、参加前の事情とか全部書いてたらえらい分量になってしまいました。気が向いたら目に付いたところだけ読んでくださいw

参加前

そもそも何があったかというと、大体去年の12月ぐらいから咳が止まらなくなり、更に年明けぐらいに高熱が出た上で咳が出続けている状態でした。

余りに咳が酷かったので、喉に傷が付いた後胃酸が逆流したりして声帯の近くに潰瘍みたいなのが出来て、まともに喋ることが出来なくなりました。

一番酷い時は、水飲むだけで苦痛だったし夜は寝れないし微熱が続くしということで、休職しようかと思ってたぐらいです。

大体その状態が2ヶ月半ぐらい続いて、本当に最悪の場合は咽頭癌の類かもしれんしこれはRubyKaigiまでに回復するか分からんぞ、ということで一旦参加を諦める覚悟をして、予定を立てるのを止めました。

その後、病院で検査した結果悪性腫瘍の可能性はほぼ無いということが分かって、そこからちょっとづつ状態が回復しました。多少心理的要因もあったのかもしれません。回復傾向になるまでは、かなり精神的にもキツかったですね。

3月後半ぐらいから概ね喋れる様になったんですが、体調がまだ不安定だったのでそこでもまだ参加は決めてませんでした。

4月前後で、Asakusa.rbの花見があって、久しぶりに人とちゃんと喋ってみて、なんとか乗り越えられたので、この辺りから行けそうだなという気になってきました。

その後、RubyKaigiのタイムテーブル解説回が4月の後半にあって、行くかどうかも決めてないのに参加したんですが、kaneko.yさんとかtagomorisさんがすげー楽しそうに自分のトーク内容をアピールしてくるので、これは行く覚悟を決めるしかないなと思い、その翌日に予定を立て直しました。

後は、悪化しないことを祈って当日へ、という感じです。

沖縄は飛行機の本数も宿も多いので、4月の終わりに予約を取っても割と何とかなったのが幸いでした。

参加中

今回、そういった事情もあってTwitterを見てくれてた人達からは、えらく体調を心配されると共に「来られて良かったですね」と声をかけていただきました。

会う人、会う人に言われたもんだから、同じことばっか喋ってるなwと思ってしまった。

でも、本当に嬉しく思いました。ご心配をおかけしましたが、ちゃんと楽しむことができましたし、コミュニティの皆さんと交流することが出来て良かったです。ありがとうございました!

その他のハイライトについては、続きで書いていこうと思います。

Day0

会場近くのVAMOSで、MNTSQさんにビールを奢ってもらいつつ、kwappaさん、ryopekoさん、森田さん、makimotoさんといった面々で飲んでました。その後、福岡のjimrockさんと合流して、jimrockさんの昔の知り合いがやってたというお店にタクシーで行き、とても地元民向けっぽいお店で二次会という感じ。

この後も大体毎日イベントの合間にビール飲んで話したりする場としてお世話になった。

煮付け(アカマチだっけ?)がすごい旨かった。後グルクンの雲丹焼きが酒を吸い取る力を持っていた。

kwappaさんと自分の仕事上の立ち位置とか、自分の中途半端さみたいなものについて話が出来たのが良かった。

ryopekoさんと同じホテルだったので、最後にコンビニで缶ビール買って延長戦したのも楽しかった!

Day1

1日目が自分にとって一番セッションが濃かった気がしている。

キーノート

とにかく、ぺんさんのキーノートが圧巻で芸術的でめちゃくちゃカッコ良かった。1000人入るホールに埋まっている聴衆が全員「ん?え?何?」ってなってる空気感を感じることって人生で早々無いだろうなと思いましたね。

使える技術の一つ一つは理解できなくはないが、それをやろうとする発想力・想像力の幅の広さが全然違うんだなということを実感した。自分もそれなりにはRubyで悪さをするタイプですが、次元が違う。TRICK 2025の開催も決まったし、来年が楽しみです。人生で一回ぐらいは応募してみたいがはてさて……。

kanekoさんのセッション

また、自分が参加を決めたkanekoさん、tagomorisさんの両名の発表が1日目にあったので、それもバッチリ聞けたのが良かった。

kanekoさんの発表は技術的にも面白かったんですが、特に凄いなと思ったのがプロジェクトのゴールというか行きたい未来をちゃんと見据えていて、そのために必要なブロックをちゃんと切り出して分かり易い問題に落としていることだと感じました。今回の発表は明確に到達可能な未来が見えていて、それに向かうチームが育ったことをアピールするという目的もあったのかなと勝手に思っています。

ちなみに、自分はRubyのParserのスタンスとしては、処理系の基盤はParser Generatorで生成するべきだと思っていて、kanekoさんの説明に納得している立場です。Rubyは結構APIファーストなデザインで作られている言語だと思っていて、これはRubyを使う人に負荷をかけずに処理系の内側を柔軟に変えられる様にする意図があるのだと思ってますが、一方でAPIファーストで何かを追加しようとした時に文法が収集付かなくなる可能性もありそうだと感じていて、安全にメンテ可能な確かな基盤として、明確な文法基盤がある方が今後のメンテにとって有効なんじゃないかと考えています。

一方、MatzがASTのAPIとしてはPrismを採用するつもりと言っていて、それについても自分は賛成です。Prismはエコシステムの対応状況をかなり意識して作られているというか、現実的な今のユースケースを既にある程度解決している所に優位性があります。RubyPrize受賞の時のKevinさんのプレゼンを聞きましたが、既にParser関連の野良gemの大半を置き換えることに成功しています。Rubyのエコシステムの中で便利に使える共通のASTインターフェースが、処理系の中でメンテされるのであれば、それだけで十分役割を果たしたんじゃないかなあと思うところです。

という訳で、今の自分にとっての理想的な共生関係は、Lramaが処理系のパーサーを担ってそこからCに限らず色々な実装のパーサーを生成可能になっていて、Rubyレベル(もしくはC拡張レベルも?)でパース後のASTを扱うインターフェースはPrismが生み出したAPIでコントロールできる、という状態が良いなと思っています。まあ、一Rubyユーザーによる勝手な妄想ですがw

yujiyokooさんのセッション

初めてRubyKaigiで見た時からファンで、毎回実機のゲーム機と自作のプレゼンツールで発表自体がデモになっているというクールな発表です。 今回はWiiのモーションセンサーをドリキャスに移植して釣りコンのモーションでスライドが操作できる様になってた所が最高に変態だと思ったポイントです。 情熱とロマンを感じますね。

後になって会場でyujiyokooさんとお話しする機会があり、同じ靴(ジャングルモック)の愛用者であることが分かり嬉しかった。相変わらずジャングルモック仲間が多いRubyKaigi。

tagomorisさんのセッション

話は変わって、tagomorisさんのNamespaceですが、これは直近のRubyの中でも一番自分に刺さるキラーコンテンツでRefinements以来のワクワク感があります。

今の理解では処理系内部のクラス・定数構造の基本形をroot namespaceでfixし、main namespace、sub namespaceと段階的にcopy on writeでクラス構造を複製しつつ変更を反映させるという構成だと理解してます。

今回は実装の詳細まで降りる時間が無かった様なので、想像でしかないんですがRubyの定数参照は非常にややこしいのでその辺りの解決と、クラス構造を複製した時に思わぬところから参照が残っていて変更が伝播しなかったり、GCで消されて死ぬというのが難しそうだなと思っています。後、今後の展開で気になったのはObject Shapeとの関係かな。必要なものがコピーされてれば、後は内側で勝手に解決されそうな気はするけど、本当か?という気もする。

Namespaceは、Railsで真面目にモジュラモノリスの区分けを行う上で非常に役に立ちそうです。RDBを共通化しつつコンテキストごとにモデルを区切っても明確に範囲が限定できるはず。また、fluentdの様なプラグイン構造を持ったアプリケーションを作る上では依存ライブラリの衝突は現実的に発生しうる問題で、特にサイレントに微妙に壊れた時が一番困るが、それを解決できそうです。他にはRailsを部分的にアップグレードして影響範囲を確認したり、bindingを引っ張り出して遊んだりしてみたいですね。UnboundMethodを引っ張り出すのは流石にメソッドエントリが一致しないので使えなさそう。

更には、ドラスティックなDSLを実現するのにも使えそうです。一定の処理領域内なら組込みクラスを弄っても変更内容を閉じ込められる。この辺りRefinementsに近いところもある。Namespaceだと変更内容はNamespace全般に伝播するので、Refinementsより便利なこともある一方で、Refinementsはスコープがより限定的なのが逆に良い点になるケースもあります。状況に応じて使い分けていきたい。(Refinementsはモジュールの探索チェーンの動きがちょっと特殊なので、Namespaceの実装時のエッジケースになりそうな気もするがw)

一方で、セッション中でも語られていた様に、gemのエコシステムにおいて古いバージョンが維持される逆向きのインセンティブが発生する懸念は理解できるし、それに関連してbundlerとの統合についても懸念する意見をTwitterで見ました。コミュニティにとってより良い機能にするためには、考えることが色々ありそうで、この辺りの意見交換では自分も貢献できそうに思いました。

参考: サプライチェーンアタックの対策について以前のRubyKaigiで登壇されていたMaciejさんのツイートからのスレッド

こんな感じで実用的かつRubyの柔軟さの幅を広げることもできそうということで自分の中では一石二鳥の機能だと思っていたんですが、Day3のMatzのキーノートで4.0の目玉になる可能性が高まっており、注目度も一気に爆上がりで思ってたよりもっと凄いことになりましたね。

という訳で、今回参加の決め手になった2人のセッションはちゃんと聞けたし、考えることも色々できて本当に良かった。アピールしてただけのことはある。

懇親会

Official Partyのチケットはギリギリで間に合ったので、参加できました。インドア派の自分でもあの会場は最高だったなー。沖縄の海辺でパーティってのは絵になる。

海風で髪がえらいことになってますが、こういう髪質なんですよww

Official Partyは色々な人に挨拶して回って、2次回はアルパカさんとNamespace期待度高いっすねーという話で盛り上がっていたと思う。この辺りから酔っ払ってて細かいことを覚えていないw

Day2

キーノート

Day2のキーノートについては、今回余りRubyKaigiらしくはなかったというか、良いトークだったしKaigi on Railsだったら諸手を上げて喜んでたトークなんですが自分がRubyKaigiのキーノートとして期待する内容とはちょっと違ってた感じですね。個人的には、もっとFiberやFiber SchedulerとFalconの深いところが聞きたかった。まあ、余りハードなトークばかりを求めるのも違うとは理解していて、単に自分の好みの話ですが……。

ydahさんのセッション

Day2で特に印象に残ったのは、ydahさんのLramaの記法拡張に関するトークです。自分は去年のRubyKaigiやKaigi on Railsの参加のKaigiEffectとしてjoker1007/tree-sitter-rbsというものを実装していました。要はシンタックスハイライトに使えるRBSのParserなんですが、これはtree-sitterというParser Generatorを使って実装していて、tree-sitterはJavaScriptDSLBNFに近いコードを記述することでParserを生成します。JavaScriptDSLであることの利点は、JSの関数の組み合わせなので関数で再帰化できるし、パラメーター化もできるし、パターン化もできるということです。

これを自分で触って実感していた後に、Lramaのparameterized ruleの話を聞いて、comma区切りのlistパターンをまとめる例などを見ると、あーこれやったことあるやつだとダイレクトに理解することができました。いやー予習してて良かった。genericの様なタグ付けをしていたら型推論が欲しくなるのも理解できるし、最終的な言語非依存のParser出力のためにはCと結合したアクション記述だと不都合も多い訳で、描いている未来にも納得感はありました。

という訳で、自分の理解度が多少高かったのもあり、とても面白く聞けました。実は自分がそう感じたのには、もうちょっと理由があったのですが、それはDay3で分かります。

午後セッション

後、午後の前半ブロックは疲れていてホテルで仮眠を取ったら思ったより寝てしまい、すっ飛ばしてしまって聞けなかった……。特にhasumikinさんのPico RubyからLramaを利用する話が気になってたんですよね。というのも上記の様にtree-sitterを触ってたこともあり、Lramaから吐いたCのパーサーをtree-sitterのカスタムパーサーとして利用できれば、エディタ界隈はシンタックスハイライトのためのパーサーをメンテしなくて良くなるのでは?とか考えたことがあったからです。それで利用する側としてのLramaの使い方に興味があったんですが、残念ながら動画待ちという感じになってしまった。

LT

今回実はCFP出してたけど落ちました。まあ想像出来る理由はいくつかあるぐらい穴があったのでそれは仕方がない。

ハイライトとしてはkanekoさんの「bisonは一つしかスタックを提供していない」と言う時の楽しそうな顔ですねw 本当に最高だった。流石に高速過ぎて脳が付いていけてたか怪しいところはあったけどw

DrinkupとRubyKaraoke

今回はちゅらデータさんのDrinkupに参加させてもらいました。沖縄民謡を聞きつつ飲んでRuby Karaokeに向けて音楽モードに気持ちを切り替えるのに相性が良かった。豚の丸焼きという凄いものが登場したんですが、流石に脂も凄かったので余り多くは食べられなかった。オードブルを作ってくれたのが、どうやらエンジニア兼イタリアンのシェフというマルチタレントな方らしく、ローストビーフローストポークなど色々と美味しかった。後やっぱ地元で食べるゴーヤチャンプルは美味いですね。

そこからRubyKaraokeに合流。なんか結果的に序盤しばらく居た部屋は、ある意味で凄く「ちゃんとカラオケ」をやってる部屋だったw 盛り上がり過ぎずそれぞれ好きな歌をちゃんと歌う、という感じで割と居心地の良い部屋になったのが良かった。 上海から来た方と米津玄師を一緒に歌ったり、相生ゆらさんに歌を褒められたのが嬉しかったですね。

その後、久しぶりにお会いしたrisacanさん、nappan23さん、mishmashtanさんと合流して、ヒプマイの一人12役を披露したり、一人オオサカディビジョンやったりしてました。喜んでもらえて良かった。盛り上がってくれるととても嬉しい。

Yuryuさんと「あいつこそがテニスの王子さま」を二人でセリフちゃんと言いながら歌えたのも楽しかった。これは10年ぶりぐらいに歌った気がするw

なんかカラオケキングとか言われてたし、どうも噂に尾鰭が付いてる様な気がしなくもないww

RubyKaraokeの時は歌うのに集中してて全然写真取ってなかったので、写真が全然無かった。もし私の写真があったらTwitterなんかで送ってくれると嬉しいです!

その後、流石に病み上がりで無理し過ぎはまずいだろうとちょっと早めに抜けて、帰っている道すがらに、何故か私の様子を見に来た野生のやんちゃさんが飛び出してきた。たまたま会うだけならまだしも、自分を狙って移動している最中にバッタリ会うとは、これが「スタンド使い同士は引かれ合う」というやつかと実感し、そのまま閉店間際のステーキヒカルに直行。二人で締めのステーキを食べることに成功しました。

この辺りで、もうRubyKaigiに来た元は完全に取れたなと確信している。

Day3

RubyKaraokeから戻った後、入眠障害が発動して睡眠に失敗した結果、午前のセッション時間に活動できないという失態。and the Worldがーーー!!とりあえず、黒曜さんのツイートを追っかけることで当日の話題に付いていくことは何とかできた。後ですれ違った時にお礼も言えて良かった。

実はSTORESさんのRuby Quizの3日目のネタに自分の名前が出てました。当日はネタバレになっちゃうので言及しませんでしたが、この記事が元ネタですね。ちょっと嬉しかったw

アーロンさんのセッション と sekiさんのセッション

Day3はアーロンさんの赤黒木の話が面白かった。昔Haskellを練習している時に赤黒木を書いたことがあったんでパターンマッチと非常に相性が良いということが分かっていたので、実装の流れはイメージしやすかった。そしてObject Shapeの性質である、基本的にキャッシュを残し続けて削除は無い、リードが多いので検索効率が大事、メモリ効率を要求するといった特性が、赤黒木のデータ構造自体が持つ性質と非常に相性が良いことが良く分かる良い発表だった。アルゴリズムを学ぶことのパワーが伝わってくる話だった。適切な知識があれば、より安全に効率よく問題を解決できることというのはしばしばあって、そのために使える材料がコンピューターサイエンスの基礎知識に転がっている。自分もまだまだなところは一杯あるが、改めて大事だなと思えた発表だった。

sekiさんのERBの話ですが、聞いてるとやっぱsekiさんのプロダクトはオーパーツなんだよなーと思えて仕方ないですね。captureの解決策とかクール過ぎるし、今から見ても有用性に対する目の付け所が良過ぎる。binding#local_variable_getの実用的な活用法が見れます。

Matzキーノート

MatzのキーノートはやっぱNamespaceへの思いを話してくれたのが熱かったですね。その話題もあってかRuby4が見えてきたかなという印象でした。そして自虐ネタは封印する様にしたらしいw まあガッカリされると言われている理由は分からんではないし、一方でそんなこと言う輩が居るがちゃんと現実を見てから言ってる?みたいなこと言いたくなるMatzの気持ちも分からんではないw

tagomorisさんの仕事の注目度が爆上がりしたのは良いことだけど、これはプレッシャーも凄そうだなーと思う。微力ながら応援しております。(ちょうどこの後の懇親会でJustinと話してた時にそういう話になった)

AfterParty

色々話せて良かったんですが特にハイライトとして覚えているのは、ydahさんと遭遇して話をした時に、ydahさんとkanekoさんの発表の想定リスナーの設定が私(joker1007)だったらしいと聞かせてもらったことです。Kaigi Effectで触発されてRBSのパーサーを書く様な人を狙って話を作れば、面白い話になりそうだし今後の仲間を探すのにも有用だと思ってもらえた様です。実際AfterParty会場でそういったtree-sitterの動きについて理解したことをydahさんに伝えることも出来たし、道理で聞いてて面白かった訳だと思いました。ちゃんと聞きに行けて本当に良かった。

追記: 改めて聞いたらydahさんが金子さんにレビューしてもらってた時にそういう話になったということでした。なので金子さんの方の真の想定リスナーは不明ですw でも近いところにあったのかもしれないなーと勝手に思っている。

後、上の階にあったカラオケ付きのバーカウンターで万葉のkukoさんと念願のカラオケが出来ました。(喉壊してたのにまた歌ってんのかって感じだけどw) 魂のルフランを一緒に歌えて楽しかったー。kukoさんめちゃくちゃ歌上手いし、お酒入ってない時に改めて行きたいなあ。

その後の二次会は、結構人数が居たんですが自分の居たテーブルがやばくて、kateiさん、なるせさん、がちゃぴん先生、ogijunさん、どみにおんさん、という感じでした。LLVM&RubyのコミッターとLinuxカーネル&Rubyのコミッターがそれぞれ自分の仕事はそんなに難しくないですよーってお互いを褒め合っていて、周りが「簡単な訳あるかー!」となってたのが本当にRubyKaigiだなーという感じで楽しかった。

その後、何故か自分がなるせさんにお前はやる気出してコミッタになるんだ!とすげー激詰めされて必死に回避してた気がします。いや、やる気が無いとか無理とかまでは思ってないのですが、心の準備とか覚悟というものがですね……。

まあ、何にせよとてもRubyKaigiらしい懇親会に参加できて最高としか言えない夜でした。

帰りがけの羽田空港で出会った最後のスタンド使いkakutaniさんとのツーショットで締め。出来過ぎだったな。

まとめ

今回、参加自体が自分の中で危うかった経緯もあって色々な人に心配をおかけしてしまいましたが、何とか元気そうな姿が見せられて本当に良かったと思っています。本当に会う人会う人に心配されてたので申し訳なかったw

(実は今もちゃんと回復したとは言えない状態で、喉の違和感や続きそうな痛みには結構注意を払っている状態です。覚悟の上でしたがRubyKaigiでかなりの負荷がかかったことは間違いないので、また状態が悪化しないかしばらく気を張ることになるでしょう。)

また、最近仕事で余りRubyを使わない領域で仕事をしているものでRuby自体とは若干関わりが薄くなっていたのですが、パRailsに関してフィヨルドの生徒の方から今年も沢山の感謝の声を聞かせてもらいましたし、ここ最近での数少ないアウトプットだったtree-sitter-rbsの活動がパーサーギャング団の想定リスナーの材料になったり、これのおかげでVSCodeからneovimに戻れたので本当に嬉しいと万葉のosatohhさんから感謝の言葉を頂いたりしました。

自分としてはちょっとしたことしか出来てないと思ってましたが、ちゃんと誰かの役に立ってたことや伝わっていたことが実感できてとても嬉しかったし、こちらの方こそ感謝しかありません。本当にありがとうございました!

コミュニティの皆さんとの繋がりを改めて実感し、RubyKaigiというのは本当に良いイベントで、自分にとっても大事なものだったなということがよく分かりました。

本当に参加できて良かった。楽しめて良かった。多くのRubyistに会えて良かった。今年もありがとうRubyKaigi!来年も無事参加できます様に!

偏りに満ちたWayland時代のLinux Desktopおすすめアプリ 2024

最近、wayland移行も大分安定して、デスクトップで利用するアプリも多少変化したので、最近利用しているものをまとめておこうと思う。 基本的にGNONEやKDE Plasmaみたいな重厚なデスクトップ環境は使わないタイプなので、そういうラインナップになっている。

開発で必須、みたいなやつはこの記事には余り入れてない。

ウインドウマネージャー

Hyprland

waylandで動作するタイル型ウインドウマネージャー。ヌルヌル動くのが気持ち良いだけでなく、機能的にもよく出来ている。開発が活発なのも良い。 waylandで画面共有のために利用されるxdg-desktop-portalのために独自実装を持っていてswayでは出来なかったウインドウ単位の画面共有が出来る。

という訳でwaylandのタイル型ウインドウマネージャーといえばswayがメジャーだが、最近はHyprlandを利用している。

IME

fcitx5

元々ibusユーザーだったのだが、waylandの入力プロトコルへの対応状況ではfcitxの方が良さそうに感じる。

ブラウザ

Firefox

Chrome及びChromiumはwayland対応が非常に微妙で、XWayland経由で起動しないと日本語を打つのにめちゃくちゃ苦労する。 英語しか打たないなら気にしなくていいのだが。 なので、Wayland環境ならFirefoxを使う方がいい。ベンダーニュートラルなのを応援するのも大事。

ステータスバー

waybar

wayland対応のステータスバー作成アプリ。設定が簡単なのが良い。やたら凝ったことをしないならこれで十分。

デスクトップ通知

dunst

wayland環境でもちゃんと思った通りに動作するし、デスクトップ環境に依存しないのが良い。

ターミナル

alacrittyも悪くないのだが、footの方はwayland専用で若干alacrittyより高速らしい。 sixel対応しているのが良い。

chafa

画像をiterm形式かkitty形式かsixel形式かsymbols(ターミナルの色指定で無理矢理出すやつ)の形式に変換して表示してくれる。 とりあえずターミナルで画像が見たい時は使える。sixel対応のfootだと便利。 とりあえずターミナルで画像を簡単に見たい時に使える。

delta

diffをside-by-sideで出せる様になるやつ

zoxide

autojumpとかzのモダンなrust実装。fzfが入ってれば履歴からも簡単に探せてディレクトリジャンプ出来る。

starship

入れるだけでshellのプロンプトを良い感じにしてくれるプロンプト生成ツール。 相当こだわりが無いならこれで良いと思う。

sheldon

汎用的なshell plugin管理ツール。zshrcの整理に使っている。。

pueue

CLIの実行をキューイングして、任意の並列数で順番に実行してくれる。たまに便利。

シェルのチートシート管理ツール。AWSのコマンドの連鎖とかを記述できるので、aws cliでlist upしてfuzzy finder噛まして検索したarnに対して操作コマンドを実行みたいなスニペットを簡単に書ける。

スクリーンショット

grim + slurp + swappy

slurpで指定したデスクトップ領域をgrimで画像にしてswappyで加工・書き込み。 コマンドにすると以下の様になる。

grim -g "$(slurp)" - | swappy -f -

色々必要でややこしいので、flameshotのwayland対応を使うのも良いかも。

ターミナルファイラー

yazi or ranger

基本的にはrangerで良いと思っているが最近余りリリースが無いので、新しいもの好きならyaziが良いんじゃないかと思っている。 rustで実装されていてやたら早いのが売りだが、MIME特定にfileコマンドを利用していて、NASにアクセスするとそこが遅い。 拡張子だけでMIMEを決めるプラグインがあるのでそっちを使う手もある。

GUIファイラー

thunar

ほとんど使わないのだが、ブラウザなどにD&Dしたい時にターミナルファイラーだけだと困るので用意してある。他のファイラーだとwaylandで動かなかったり、GNOMEとかKDEの必要無い依存がガッツリ入ったりするので、バランス取れてるのがthunarだった。

動画プレイヤー

mpv

これは昔から変わらない。これ一択だと思う。

音楽プレイヤー、オーディオ関連

mpd + cantata

mpdならDSDのハードウェアデコード再生が出来るし、システム移行しても潰しが効きそうってことで採用している。まあ、そんなDSDハイレゾ音源ほとんど持ってないんだけど……。 再生のフロントエンドはcantataを利用している。開発がほぼ止まっているのだが、これより便利なフロントエンドが無い。ここは不安な点の一つ。

qpwgraph

pipewireの接続状態を可視化してくれるツール。GUIで音声出力先を一時的に切り替えたり、pipewireのプラグインとして実行しているノイキャンに繋いだりバイパスしたりできる。

音源タグ編集

kid3-qt

特筆すべき点は無いけど必要十分という感じ。

画像ビューワー

vimiv-qt or nsxiv or mcomix

waylandネイティブで起動できて軽さとシンプルさで選ぶとvimivだった。 wayland対応にこだわらないならnsxivでも良い。 zipにまとめた画像ファイルはmcomixで見ている。

壁紙設定

swww + waypaper

wayland環境での壁紙設定にはそれ用のアプリが要る。 hyprpaperとかswaybgの方がシンプルな気はするけど、壁紙切り替え時にエフェクトが入るという地味かつ無駄なオシャレ感でswwwを利用している。 壁紙の選択はwaypaperがswwwバックエンドに対応していたのでこれを利用している。

mpvpaper

動画ファイルを無限ループする壁紙にしてくれる。live wallpaperで検索するとオシャレな壁紙が手に入る。 タイル型マネージャーを使ってると、正直リソースの無駄なのだが、オシャレ感が欲しい時にw

ランチャー

wofi

シンプルで応用が効くし、そもそもがwayland向けなのでrofiみたいにwayland forkを利用したりする必要がない。 desktopエントリの起動だけじゃなくてPATHにあるコマンドの起動にも使える。

クリップボード管理

wl-clipboard + cliphist + wofi

wl-clipboardで変更をwatchして変更があったらcliphistに溜める。 キーボードショートカットでcliphistから読み出したものをwofiに渡して表示する。選択したらそれをclipboardに書き出す。という感じ。 ただ、cliphistに溜めたやつを細かく制御できないので、パスワードマネージャーからコピーした値などにセキュリティ上の懸念が多少ある。

Bluetooth

blueman

linuxBluetoothを使うなら、まずbluezが必要でそのCLIだけで十分な場合もあるが、GUIで操作したい時はbluemanを使っている。 GTKに多少依存しているぐらいでシンプルなのでデスクトップ環境を選ばないのが良い。余計なものを余り入れなくていい。

プレゼンツール

pdfpc

PDFをkeynoteのプレゼンモードみたいに表示できるツール。発表者メモも書ける。

VPN

tailscale

これを入れておけば困ることはない。革命的に便利過ぎる。個人用途なら大抵の場合無料で十分。200台ぐらいデバイス持ってる変態はお金出してくださいw