Ruby
イベント
マガジン
技術ブログ
はじめに みなさんこんにちは、ワンキャリアでデータサイエンティストをしているヨウです。中国・蘇州出身で、日本の大学・大学院を経て、新卒でワンキャリアに入社しました。
Developer Engagementブロックの @ikkou です。2026年4月22日から24日の3日間にわたり北海道は函館市の 函館サーモン・まるなまアリーナ で「 RubyKaigi 2026 」が開催されました。 日本Rubyの会「RubyKaigi 2026」特別ライトアップ 今回の函館開催にあわせ、通常の白色のみの五稜郭タワーのライトアップが、Rubyをイメージした特別色のレッドにライトアップされていました。 ZOZOは今年もプラチナスポンサーとして協賛し、スポンサーブースを出展しました。 technote.zozo.com 本記事では、前半はWEARのバックエンドエンジニアが気になったセッションを紹介します。後半では、ZOZOの協賛ブースの様子と各社のブースにおけるコーディネートを写真中心に報告します。 ZOZOとWEARとRubyKaigi WEARのバックエンドエンジニアが気になったセッション A Faster FFI そもそもFFIとは? 現状のFFIの課題 なぜFFIは遅いのか? 改善1 改善2 FFXの仕組み 最終的なパフォーマンス ruby.wasm can also enable JavaScript to call Ruby libraries. The Less-Told Story of Socket Timeouts なぜopen_timeoutが必要だったのか net/httpのtimeoutライブラリ依存問題 resolv_timeout + connect_timeoutでは代替できない open_timeoutのAPI仕様 試してみる Ruby 3.4: fast_fallback ON Ruby 3.4: fast_fallback OFF Ruby 4.0: open_timeout fast_fallback ON + open_timeout fast_fallback OFF + open_timeout 同時指定はエラー 全体の対比 おわりに Autoresearching Ruby Performance with LLMs Autoresearchとは なぜ「ループ」が重要なのか 4つのループパターン Ralphループ Autoresearchループ Factoryループ Sidekiqでの実証実験 実験の背景 "マージされないコードを生成することの意味" PRレビューとは何か? 4つのレッスン Lesson 1: 「自動調査」であって「自動変更」ではない Lesson 2: 自分がオーナーで Architect でないものに Autoresearch を適用しない Lesson 3: ループはビター・レッスンを実践する Lesson 4: 人間のゲートをソフトウェアゲートに変換する Software Factory とその課題 まとめ おわりに Exploring RuboCop with MCP The Journey of Box Building Ruby Boxとは何か Ruby Boxの仕組み Ruby Boxが生まれた歴史 おわりに ZOZOブースの紹介 協賛企業ブースのコーディネートまとめ RubyKaigi 2026 アフターイベント〜初参加LT・スポンサー4社のパネル〜を開催します おわりに ZOZOとWEARとRubyKaigi 私たちが運営する ファッションコーディネートアプリ「WEAR by ZOZO」 のバックエンドはRuby on Railsで開発されています。2013年にVBScriptで構築されたシステムでしたが、2020年頃からコードフリーズし、Rubyへのリプレイスを開始しました。現在もリプレイスを進めながら、新規の機能もRubyで開発しています。また、Matzさんを技術顧問としてお迎えし、毎月Matz MTGと称したオンラインミーティングを実施しています。 ZOZOとRubyKaigiの関係は、ZOZOの前身であるVASILY時代の RubyKaigi 2017 に遡ります。コロナ禍を経て再開した RubyKaigi 2022 からはWEARのバックエンド開発を担うチームが中心となって協賛とスポンサーブースの出展を続けています。 RubyKaigi2017参加レポート(全日分)とスライドまとめ RubyKaigi2018参加レポート RubyKaigi 2019参加レポート〜sonots登壇セッション & エンジニア8名による厳選セッション RubyKaigi 2022参加レポート 〜エンジニアによるセッション紹介〜 RubyKaigi 2023参加レポート 〜エンジニアによるセッション紹介〜 RubyKaigi 2024 参加レポート RubyKaigi 2025 協賛&参加レポート WEARのバックエンドエンジニアが気になったセッション 今年はWEARチームから6名のバックエンドエンジニアがRubyKaigiに参加しました。本パートでは各エンジニアが特に気になったセッションを個々の視点で紹介します。 A Faster FFI chika です。私からはAaron Patterson( @tenderlove )氏の「 A Faster FFI 」を紹介します。 このセッションでは、「RubyはC言語より速くなるか?」という問いからスタートし、具体的にはRubyのFFIを高速化し、ネイティブのC言語(C拡張)よりもRubyを速く実行できるか? というのがメインの議題でした。 余談ですが、Aaron氏はRubyKaigi 2026の外国人登壇者の中でおそらく唯一日本語でスピーチする方です。流暢な日本語に加えて時折ジョークも交え、大変ユニークなセッションであり毎年楽しみに拝聴しています(最初の挨拶では「外国人みたいな名前ですが、外国人です」と日本語でジョークを言っていて面白かったです)。 そもそもFFIとは? FFIとはForeign Function Interfaceの略称で、一般的にはRubyのような高水準言語からC言語やRust, Zigなどで書かれた外部の関数を呼び出すための「概念」のことを指します(FFI自体は、あるプログラミング言語から他のプログラミング言語で定義された関数などを利用するための機構)。 Rubyでは主にlibffiというライブラリやfiddle gemを介して利用され、プラットフォームごとの呼び出し規則の違いを吸収してくれます。 例:FFIを使ったhello world #include <stdio.h> void hello ( void ) { printf ( "Hello, World from C! \n " ); } require ' ffi ' module Hello extend FFI :: Library ffi_lib File .expand_path( ' libhello.dylib ' , __dir__ ) attach_function :hello , [], :void end Hello .hello cc -shared -o libhello.dylib hello.c && ruby hello.rb #=> Hello, World from C! FFIはネイティブC拡張に比べ、CRuby, JRuby, TruffleRubyといった異なるRuby実装でも、ほぼそのまま動くというポータビリティの観点でもメリットがあります。 現状のFFIの課題 一見するとRubyからC言語の関数が直接呼び出せるのはパフォーマンスや移植性などの観点から便利そうに見えますが、実際のところFFIは滅多に使われておらず、その理由としてパフォーマンスが悪いという点が挙げられていました。 ベンチマーク比較では、既存のC拡張とFFIを比較すると、ネイティブC拡張の方が約2.4倍も高速に動作しているとのことでした(FF愛(FFI)してない…)。 なぜFFIは遅いのか? FFIが遅い主な原因として、以下の三点を挙げていました。 余分なフレームプッシュ Rubyから目的のC言語の関数を最終的に呼び出すまでに、呼び出し規則の変換などを行う「中間的な関数」がFFIやVM内部で何層も呼び出される。そのたびにコールスタックに余分なフレームが積まれる(プッシュされる)ため、関数呼び出しそのもののオーバーヘッドが大きくなってしまう。 値の渡し方の違い Rubyはスタックを使って値を渡すが、C言語は(x86_64やARM64などの環境において)CPUのレジスタを使用して値を受け取る。そのため、C言語の呼び出し規約(ABI)に合わせて、スタックからレジスタへ値をコピーして詰め直す操作が発生する。 タイプ変換 RubyのオブジェクトをC言語が理解できる型に変換し、Cからの戻り値を再びRubyオブジェクトに変換するオーバーヘッドが発生する。 改善1 この遅い原因を解消するために、JITコンパイラを用いてそれらのコストを削減するアプローチを試みていました。そして、Rubyで書かれたFFIのためのJITコンパイラ「FJIT」が誕生しました。これはfiddle gemなど複数のgemの機能を活用して直接マシンコードを生成することで、既存のFFI実装に比べて約2倍ほど高速化することが可能となりました。 改善2 さらにもう1つの解決案として提案されたのが、CRuby内蔵のJITコンパイラであるZJITと、新しいトランスレータである FFX でした。 FFXの仕組み FFXは、Rubyで書かれたFFI拡張のコードを読み込み、自動的にC拡張のコードに変換します。この時、生成されるCコードの中にはZJIT向けのヒント(具体的には、生成されるCコードの中に「この関数の引数はこういう型である」といったメタデータをZJITが読み取れる形で配置する)が意図的に埋め込まれます。ZJITはこのヒントから関数の引数や戻り値の型情報を認識することで、最適化された高速なマシンコードを動的に生成することが可能となります。 最終的なパフォーマンス これらの改善を導入しベンチマークを測定したところ、なんとC拡張よりも約1.4倍速く動作するという結果になりました。結果的に、「RubyはCよりも速くなる?」という問いに対しては、「(JITとFFXの力を借りれば)イエス」となり、つまり「RubyはCよりも速い」という結論で締め括られていました(笑) FFIの「遅いから使わない」という常識が覆りつつあり、ZJITやFFXの成熟によってRubyからCライブラリを気軽に・高速に呼べる未来が近づいていると感じました。 ruby.wasm can also enable JavaScript to call Ruby libraries. 小島( @KojimaNaoyuki )です。私からはShigeru Nakajima( @ledsun )さんの「 ruby.wasm can also enable JavaScript to call Ruby libraries. 」を紹介します。 www.docswell.com このセッションでは、ruby.wasmのこれまで積み重ねられてきた改善を振り返りつつ、なぜまだ実務での採用事例が少ないのかの分析とそれを解消するために「JSからRubyを呼び出す」機能を強化していく方針が話されていました。 ruby.wasmとは、ブラウザ環境でRuby実行を可能にするものです。JavaScriptと相互で連携しながらRubyをブラウザ環境で実行できます。 ruby.wasmはこれまでRubyからJavaScriptを呼び出す改善を多く実施しています。しかし、現状、本番環境で利用されている例はあまりないそうです。その原因としてこのセッションでは以下の2点について言及されていました。 ruby.wasmバイナリが大きすぎる JSからRubyを呼ぶサポートが弱い ruby.wasmバイナリが大きすぎることについては https://cdn.jsdelivr.net/npm/@ruby/head-wasm-wasi@2.9.3-2.9.4/dist/ruby+stdlib.wasm を例に出して話されていました。こちらのファイルは非圧縮で31.80MiB、圧縮すると8.84MiBになるそうです。そして、BtoCのブラウザアプリケーションではダウンロードサイズが重要ですが、BtoBの場合はダウンロードサイズは大きな問題ではない可能性があるとセッションでは話されていました。 JSからRubyを呼び出すサポートが弱いことについては、RubyからJSを呼び出す場合とJSからRubyを呼び出す場合を比較して扱いやすさの違いを言及していました。以下がセッションで使用されていた表です。 Feature R → J J → R eval Yes Yes Method Call Yes call Property Access Yes N/A Object Conversion Yes string Load Ruby Scripts browser No 出典: ruby.wasm can also enable JavaScript to call Ruby libraries. | ドクセル (最終閲覧日:2026/05/07) こちらを見ると確かにJSからRubyを呼び出す場合にサポートされていないものが多い印象ですね。現状JSからRubyを呼び出すにはevalメソッドを利用して実行できますが、rubyの実行結果の戻り値を扱う適切なAPIが不足していたりと課題があるようです。 これらの現状を受けて、本番環境で利用されている例が少ないのは「JSからRubyを呼ぶ」機能が不十分であるからではないかと分析されていました。そして、もしJSからRubyを呼ぶことが簡単にできたなら、ロジックの二重実装を防ぐことができるため有用ではないかとおっしゃっていました。ロジックをRubyに統一できれば修正も容易になり保守性も向上しそうですね! 「JSからRubyを呼ぶ」機能を強化していくためにはRubyオブジェクトをラップするRbValueを拡張する必要があり、このプロジェクトを「Ruby’s Blanket」と呼ぶと発表がありました。ちなみに、「Ruby’s Blanket」という命名は函館出身の人気バンドGLAYの楽曲に由来しているともおっしゃっていました。 セッションでは、目指していきたい理想の姿も紹介されており、以下のような使用感を想定しているようです。 vm . evalFile ( “ [ dog . rb ]( http: //dog.rb)”); const dog = vm .eval ( “Dog . new” ) ; dog . vow () ; dog . vow () ; const count = dog . count_of_vows . toInt () ; 出典: ruby.wasm can also enable JavaScript to call Ruby libraries. | ドクセル (最終閲覧日:2026/05/07) JSネイティブなプロパティアクセスやメソッド呼び出しでRubyプログラムを扱っているところを示しています。すごく直感的にJSからRubyコードを呼び出せていますね! セッションの最後にはコーディングエージェントの活用についても語られていて、以前よりも難しい課題に取り組むことができていると語られていました。「Ruby Committers and the World」や「Matz Keynote」でもAI活用が議題に上がっており、多くのコミッターがAIを駆使してRubyの改善に当たっているそうです。 個人的には、言語実装のような「世界的にサンプルが少なそうな低レイヤーの領域」はAIが不得意な分野だと思い込んでいたため、これほど多くのコミッターが活用している事実に驚かされました。 Ruby本体や周辺ライブラリの開発が、AI活用によって今後さらに加速していくのが非常に楽しみです! このセッションを通して「JSからRubyを呼ぶ」機能が充実することで、Rubyの活用の幅が広がる未来を感じました。実際のプロダクトのブラウザ上でRubyが動いているところを目にすることを期待しています! The Less-Told Story of Socket Timeouts WEARのバックエンド開発を担当しているaao4seyです。私からは、 @coe401_ さんの「 The Less-Told Story of Socket Timeouts 」を紹介します。 このセッションでは、Ruby 4.0で Socket.tcp / TCPSocket.new に新しく加わった open_timeout の導入経緯と、その背後にあるsocketライブラリのタイムアウトの歴史が語られていました。本記事では open_timeout が必要になった経緯を発表内容を踏まえて整理し、続いて各Rubyバージョンで実際にタイムアウトの挙動がどう変わるのかを手元で観察した結果を紹介しようと思います。 なぜopen_timeoutが必要だったのか net/httpのtimeoutライブラリ依存問題 発表者のもとに「net/httpのtimeoutライブラリ依存を外したい」という相談が届いたことが、 open_timeout 導入のきっかけでした。スライド内でも紹介されていますが、Ruby 2.7の開発時代にもnet/httpのような標準ライブラリが、標準ではないライブラリに依存しないほうが良いのではという趣旨の提案がなされていました。 加えて、timeoutライブラリは内部で Timeout::State::GLOBAL_STATE という共有状態を持っているため、non-main Ractorからこれにアクセスすると Ractor::IsolationError が発生してしまう状況でした。 resolv_timeout + connect_timeoutでは代替できない Ruby 4.0開発時点で、 Socket.tcp / TCPSocket.new には名前解決のタイムアウトを指定する resolv_timeout と、接続確立のタイムアウトを指定する connect_timeout がすでに用意されていました。これらを組み合わせればtimeoutライブラリの代替になりそうにも思えますが、実際にはメソッド全体の絶対上限時間を制御できません。 ここで前提として1つ補足しておくと、Ruby 3.4で Socket.tcp / TCPSocket.new にはHappy Eyeballs Version 2 (HEv2 / RFC 8305)が fast_fallback という名前でデフォルト有効として導入されました。HEv2は複数候補のIPアドレスに対して名前解決と接続試行を並行実行するアルゴリズムです(HEv2 自体の解説は同僚が「 RubyKaigi 2025 協賛&参加レポート 」で書いているので、本記事ではそちらに委譲します)。なお、Ruby 3.4までは fast_fallback: false 経路の resolv_timeout がAPI onlyで実装されておらず、本記事の対象であるRuby 4.0で両経路で機能するように修正されています。 その上で、たとえばfast_fallback有効で resolv_timeout: 2000ms , connect_timeout: 1000ms を指定した場合、IPv6が先に解決されてIPv4が解決待ちのまま接続試行が始まると、 connect_timeout の1000msを過ぎても resolv_timeout の期限まで待機が続きます。結果、全体タイムアウトは2つの合計でも connect_timeout の値でもなく、 resolv_timeout の値である2000msに支配されてしまいます。 HEv2のConnection Attempt Delay (250ms)が絡むケースでは「 connect_timeout: 1000ms を指定しても全体は1250ms待つ」、fast_fallback無効では「IP数 × connect_timeout 」と、いずれもユーザーが指定した値とは別の値で全体時間が決まってしまいます。 open_timeoutのAPI仕様 これらの背景を受けて、発表者ご自身が open_timeout を Socket.tcp / TCPSocket.new に追加する提案をされました。 open_timeout はtimeoutライブラリと同じく「名前解決〜接続確立の全体」を1つの期限で管理するオプションです。 Socket .tcp( " ruby-lang.org " , 80 , open_timeout : 1 ) 設計上の論点として、 open_timeout を resolv_timeout / connect_timeout と併用された場合にどう振る舞うかを整理する必要がありましたが、複雑になりすぎてしまうという課題があり、最終的に open_timeout は connect_timeout / resolv_timeout との同時指定を禁止し、同時指定時は ArgumentError を投げる仕様となったそうです。 発表内では実装の細かい部分が fast_fallback ありなしごとに説明されていました! スライドのp.224以降に記載されています。 試してみる ここまで見てきた経緯を踏まえて、 open_timeout がRuby 4.0で動作するのかの検証をしてみます。また、私自身はRuby 3.4で導入された fast_fallback 機能もこの発表内で知ったので、こちらの挙動も合わせて確認してみることにしました。 実験のため /etc/hosts に応答しない3つのIPを割り当てた test-multi-local.wear.jp を用意しました(記載のIPはdummyです)。 192.168.xx.1 test-multi-local.wear.jp 192.168.xx.2 test-multi-local.wear.jp 192.168.xx.3 test-multi-local.wear.jp 接続先には応答しないポート(50000)を指定し、SYNを送っても応答が返ってこない状況を作ります。 Ruby 3.4: fast_fallback ON この場合、HEv2のアルゴリズムに従い以下のような挙動になるはずです。 約250ms間隔(Connection Attempt Delay)で次のIPへ並行に試行を発射している 期待結果: (IP数-1) × 250ms + connect_timeout = 1.5 秒 で全体タイムアウト 検証スクリプト require " socket " t = Time .now begin Socket .tcp( " test-multi-local.wear.jp " , 50000 , connect_timeout : 1 , fast_fallback : true ) { |s| s.close } rescue => e puts "#{ e.class } : #{ e.message }" ensure puts " elapsed= #{ Time .now - t } s " end 実行結果(Ruby 3.4.9) Errno::ETIMEDOUT: Operation timed out \- user specified timeout elapsed=1.51281s tcpdumpの結果抜粋 10:41:18.580 SYN → .1 ← IP1 開始 (t=0) 10:41:18.836 SYN → .2 ← IP2 開始 (t≒0.256) 10:41:19.086 SYN → .3 ← IP3 開始 (t≒0.506) 想定どおり、約1.5秒でtimeoutエラーになることが観測できました! また、tcpdumpでSYNパケットをざっくり観測した結果、約250msごとに異なるIPへSYNパケットが飛んでいることも見て取れました。 Ruby 3.4: fast_fallback OFF fast_fallback: false を渡すと、Ruby 3.3以前と同じ直列フォールバックの挙動に戻ります。以下のような挙動になるはずです。 各IPに対して connect_timeout: 1 で1秒ずつ順番に試行 期待結果: IP数 × connect_timeout = 3 秒 で全体タイムアウト 検証スクリプト 前述のスクリプトの Socket.tcp 呼び出し行を以下のように変更します。 - Socket.tcp("test-multi-local.wear.jp", 50000, connect\_timeout: 1, fast\_fallback: true) { |s| s.close } + Socket.tcp("test-multi-local.wear.jp", 50000, connect\_timeout: 1, fast\_fallback: false) { |s| s.close } 実行結果(Ruby 3.4.9) Errno::ETIMEDOUT: Operation timed out \- user specified timeout elapsed=3.013505s tcpdumpの結果抜粋 10:40:20.753 SYN → .1 ← IP1 開始 (t=0) 10:40:21.760 SYN → .2 ← IP2 開始 (t≒1.0) 10:40:22.764 SYN → .3 ← IP3 開始 (t≒2.0) 想定通り、fast_fallbackを無効にするとIP1 → IP2 → IP3と1秒ずつ順番に試行され、合計約3秒で全体タイムアウトすることが観測できました。 Ruby 4.0: open_timeout ここまでの結果から、 connect_timeout だけではメソッド全体の上限時間を制御できないことが見えました。Ruby 4.0で導入された open_timeout を使うと挙動がどう変わるかを見ていきます。 fast_fallback ON + open_timeout fast_fallback ONで open_timeout が機能するか試します。 期待結果:open_timeoutに指定した1秒でタイムアウト 検証スクリプト 前述のスクリプトの Socket.tcp 呼び出し行を以下のように変更します。 \- Socket.tcp("test-multi-local.wear.jp", 50000, connect\_timeout: 1, fast\_fallback: true) { |s| s.close } \+ Socket.tcp("test-multi-local.wear.jp", 50000, fast\_fallback: true, open\_timeout: 1) { |s| s.close } 実行結果(Ruby 4.0.3) IO::TimeoutError: user specified timeout for test-multi-local.wear.jp:50000 elapsed=1.004653s 想定通り、約1.0秒で IO::TimeoutError が発生し、 open_timeout: 1 で指定した時間とほぼ同じ時間でタイムアウトすることが観測できました! fast_fallback OFF + open_timeout fast_fallback OFFで open_timeout が機能するか試します。 期待結果:open_timeoutに指定した1秒でタイムアウト 検証スクリプト 前述のスクリプトの Socket.tcp 呼び出し行を以下のように変更します。 \- Socket.tcp("test-multi-local.wear.jp", 50000, connect\_timeout: 1, fast\_fallback: true) { |s| s.close } \+ Socket.tcp("test-multi-local.wear.jp", 50000, fast\_fallback: false, open\_timeout: 1\) { |s| s.close } 実行結果(Ruby 4.0.3) Errno::ETIMEDOUT: Operation timed out \- user specified timeout for 192.168.xx.1:50000 elapsed=1.007804s こちらも想定通り、約1.0秒で user specified timeout が発生し、 open_timeout: 1 で指定した時間とほぼ同じ時間でタイムアウトすることが観測できました! fast_fallback の設定有無に関わらず open_timeout が機能していることがわかりました。 同時指定はエラー open_timeout は connect_timeout / resolv_timeout との同時指定が禁止されており、 Socket.tcp の入口で ArgumentError が即座に発火します。 Socket .tcp( " test-multi-local.wear.jp " , 50000 , connect_timeout : 1 , open_timeout : 1 ) { |s| s.close } # => ArgumentError: Cannot specify open_timeout along with connect_timeout or resolv_timeout 全体の対比 ここまで観測した結果を並べると以下のようになります。 Ruby 設定 elapsed 3.4.9 fast_fallback: false , connect_timeout: 1 約 3.01 秒 3.4.9 fast_fallback: true , connect_timeout: 1 約 1.51 秒 4.0.3 fast_fallback: false , open_timeout: 1 約 1.01 秒 4.0.3 fast_fallback: true , open_timeout: 1 約 1.00 秒 connect_timeout だけを指定したケースでは、ユーザーの指定値とは別の値で全体時間が決まっていたのに対し、 open_timeout を指定すれば 指定値ぴったりで打ち切られる ようになりました。 おわりに 本パートではセッションの内容である open_timeout の導入経緯と、実際に fast_fallback や open_timeout の挙動を観測した結果を紹介しました! open_timeout の実装により、接続試行全体のタイムアウトが管理しやすくなったと感じます。実際のセッションではRuby 2.0の開発時代から今に至るまでのsocketライブラリのタイムアウト導入の歴史も詳細に説明されており、各時代ごとにどんな課題がありどう解決してきたのかを知ることができとても興味深かったです。また、このセッションに限らずですが、RubyKaigi全体を通して発表者の方のモチベーションを垣間見ることができたのも自分にとって良い刺激となりました。 Autoresearching Ruby Performance with LLMs 小山 です。私からはNate Berkopec( @nateberkopec )さんの「 Autoresearching Ruby Performance with LLMs 」を紹介します。 Berkopec氏は、Speedshop代表でありPumaメンテナー、Railsパフォーマンスコンサルタントとして広く知られています。本セッションは Autoresearch という先行ツールを参考に、AIエージェントを使って、Rubyのパフォーマンス問題を自動で調査する取り組みとそれにより得られた示唆の発表でした。 発表資料は以下のリポジトリで公開してくださっているためご参照ください。 github.com Autoresearchとは Autoresearchは、2026年3月にAndrej Karpathy( @karpathy )氏が公開したLLM実験自動化ツールです。仕組みはシンプルです。 AIエージェントがコードへの変更を提案・適用する 一定時間ベンチマークを実行する ベースラインより良ければコミット、悪ければリバート 1に戻る これを無限に繰り返します。PyTorchと単一のメインファイル以外に依存関係がなく、1時間あたり約12実験、一晩で約100実験を回せます。Berkopec氏がこのコンセプトをRubyのパフォーマンス改善に応用できるかを探ったのが今回の発表です。 なぜ「ループ」が重要なのか セッションで繰り返し強調されたのが ループ という概念です。 AIエージェントは本質的にはループにすぎないと、Berkopec氏は次のコード例で説明しています。 messages = [user_prompt] loop do reply = llm.call(messages, tools : TOOLS ) break puts(reply.text) unless reply.tool_call? result = run_tool(reply.tool_name, reply.arguments) messages << reply messages << tool_result(result) end このループに どのようなゲート(通過条件)を置くか が、ループの性格を決めます。それがセッション全体の一貫したテーマでした。 4つのループパターン Berkopec氏は、ゲートの種類によってループを4種類に分類しました。 ループ ゲート シグナル 成果物 Agents LLM 自己停止 離散 最終的な返答 Ralph ビルド+テスト 離散 グリーンなコミット Autoresearch ベンチマーク差分 連続 改善した diff Factory 多数のチェック 多変数 マージ可能な PR Ralphループ while :; do cat PROMPT.md | claude-code ./build \_ and \_ test || continue git add \- A git commit \- m " ralph: passing build " git push done ビルドとテストをゲートにしたループです。テストが通った変更だけをコミットし続けます。バグへの対処に向いています。 Autoresearchループ best \= benchmark loop do change \= agent.propose\_optimization apply(change) score \= benchmark if score \> best git\_commit(change.summary) best \= score else git\_revert end end ゲートがブール値(pass/fail)ではなく 連続値(ベンチマークスコアの改善量) である点がRalphとの違いです。 Factoryループ backlog.each do |spec| loop do code \= agent.implement(spec) gates \= scenarios.map { |s| s.run(code) } break if gates.all?(& :pass? ) end ship(code) end スペックからコードを生成し、複数のゲートを全て通過したら出荷するパターンです。StrongDM社の「 Software Factory 」が参照されていました。 Sidekiqでの実証実験 セッション中に紹介された実験の1つが、 pi-autoresearch を用いた、Sidekiqを対象にした自動最適化です。 実験の背景 Sidekiqの Processor::Counter はアトミックなカウンターで、16行程度のシンプルな実装です。Autoresearchエージェントはここにストライプドロック(各スレッドが自前の状態を持ち、書き込みを安くする仕組み)を適用する変更を提案しました。 また、別のPRでは Time.now.to_i を Process.clock_gettime に置き換えることも提案されました。 Comparison: Process.clock_gettime: 17294486.8 i/s Time.now.to_i: 12698329.6 i/s - 1.36x slower Timeオブジェクトを生成せずに整数を直接返すことで1.36倍高速化できます。 "マージされないコードを生成することの意味" これらの変更についてBerkopec氏は「OSSメンテナーは実際にはこれをマージしないだろう」と正直に述べていました。その上で、なぜマージできないコードを生成するのか、という問いを投げかけました。 "Why generate code that you can't merge?" この問いに対して、セッションは PR レビューの目的を再定義するパート へと展開しました。 PRレビューとは何か? 変更を却下する理由として、Berkopec氏は以下を挙げました。 バグがある → LLMはある程度得意 トレードオフがある →「十分に速い」とはどのレベルか? 複雑すぎる → Flog/Flayスコア、ABCスコア、LOC リスクが高すぎる → キャッシュ無効化など(Autoresearchはキャッシュが大好きで、しかもバグりやすい) テストを通るが... → Autoresearchは良いテストがある前提で動く GPL 違反など → GitHub外の法的・コンプライアンスチェック 要するに 「マージできるか」を判断する能力こそがArchitectである という主張です。 4つのレッスン セッションを通じて提示された4つのレッスンをまとめます。 Lesson 1: 「自動調査」であって「自動変更」ではない Stan Lo氏(Shopify)の取り組みが紹介されました。Ruby-lspのCI時間を33%削減、rubydexのインデックス速度を10%〜50%改善するといった成果を、AIの提案を自身がレビューした上で実現しています。AIの出力を人間が検証・理解して取り込む姿勢が重要だという例です。 Lesson 2: 自分がオーナーで Architect でないものに Autoresearch を適用しない 理解・検証・修正できるコードにのみ適用すべきです。 Lesson 3: ループはビター・レッスンを実践する Richard Suttonさんの「苦い教訓(Bitter Lesson)」(人間の知識を組み込むより、計算のスケールによって探索・学習する方が長期的に優れるという教訓)をループは体現しています。ループは人間のゲートを担保するバージョンに過ぎず、ゲートをソフトウェアに置き換えることで探索をスケールさせられます。 Lesson 4: 人間のゲートをソフトウェアゲートに変換する 「遅い」という感覚を赤/緑の状態・バグチケットに変換するプロセスがパフォーマンス改善の本質です。たとえAutoresearchが失敗しても、ゲートを定義する過程でソフトウェアの品質自体が上がります。 Software Factory とその課題 セッション後半では、 Software Factory (StrongDM社の提唱するコンセプト)が紹介されました。 "Code must not be written by humans. Code must not be reviewed by humans." — StrongDM's "Software Factory" これは人間のレビューを完全に排除し、多数の自動ゲートを通過したコードのみを出荷する考え方です。セッションではこれを「dark factory」と表現し、Berkopec氏は多くの未解決の問いを並べて紹介しました。 形式手法・プロパティベーステストは現実的にスケールするか? 既存の大規模コードベース(ブラウンフィールド)に適用できるか? LOCの肥大化をどう防ぐか? LLMがトレーニングデータの著名な最適化を「再発明」するだけにならないか? 決定論的なゲートとLLMジャッジをどう組み合わせるか? Berkopec氏はこれらに対して現時点で答えはないとしながらも、「ゲート設計こそが重要になる」という方向性を示しました。 まとめ Berkopec氏のセッションを一言でまとめると、 「AI に自律的なループを回させることはできるが、それを意味のある成果に変えるにはゲート設計という人間のスキルが必要」 というメッセージでした。 発表で推奨されていた実践ステップは以下の通りです。 自分が十分に理解・レビューできるコードにのみ適用する(Stan Lo氏のアプローチ) 成熟した小さなモジュールから始める ゲートが最小で済む場所からスタートする ゲートを増やせば人間のレビューを減らせる Ralph(離散)・Autoresearch(連続)・Factory(多ゲート)のいずれかのループを試してみる おわりに 今年のRubyKaigiはAIに関するセッションの多さが印象的でした。普段の業務で自身もAIを活用しています。本セッションで紹介されていたゲートの設計・導入に注力して、パフォーマンス改善はもちろんのこと、それ以外のあらゆる業務課題の自動化をより推進していきたいと思いました! Exploring RuboCop with MCP 伊藤です。今年もRubyKaigiに参加してきました! 私からは @koic さんの「 Exploring RuboCop with MCP 」をご紹介します。 speakerdeck.com 本セッションは「I. MCP Ruby SDK」「II. RuboCop x MCP」の2部構成で、前半では @koic さんがコミッターを務める公式の MCP Ruby SDK ( mcp gemとして公開)の設計や、Streamable HTTPでのセッション管理・Sampling・Elicitationまでが解説されました。後半は、RuboCop 1.85.0で実験的に追加された組み込みMCPサーバーの紹介でした。 ここでは特に後半「RuboCop x MCP」を題材に、WEARのバックエンドエンジニアとして実際に手元で動かしてみた感想を中心にご紹介します。 rubocop --mcp は、RuboCop 1.85.0から導入されたサブコマンドで、AIエージェント(Claude CodeなどのMCPクライアント)からRuboCopを呼び出せるようにする組み込みMCPサーバーを起動します。stdio transportで通信し、以下の2つのツールを公開します。 rubocop_inspection :オフェンス検査(読み取り専用) rubocop_autocorrection :オートコレクト適用(破壊的) なお、RuboCopではコーディング規約違反のことを「オフェンス(offense)」と呼びます。以降本記事でもこの用語を使います。 rubocop --lsp の内部で使われている診断機構( RuboCop::LSP::Runtime )をそのまま再利用しているのが特徴で、出力されるオフェンスはLSPの Diagnostic 形式(JSON)です。Clang形式のテキストよりも構造化されているため、AIエージェントがそのままパースして扱いやすい形になっています。 ここからは実際に手元で動かしてみます。 rubocop --mcp を使うには、RuboCop本体に加えて mcp gemが必要です(RuboCopは mcp をruntime dependencyとして持っていないため)。既存のGemfileのdevelopmentグループに以下を追加します。 group :development do gem ' mcp ' end 次にClaude Codeに .mcp.json でRuboCopのMCPサーバーを登録します(Claude Codeが設定を読み、起動時に bundle exec rubocop --mcp をサブプロセスとして立ち上げてくれます)。 { " mcpServers ": { " rubocop ": { " command ": " bundle ", " args ": [ " exec ", " rubocop ", " --mcp " ] } } } Claude Code上で /mcp を叩くと、登録した rubocop サーバーから2つのツールが認識されているのが確認できます。 ❯ /mcp ──────────────────────────────────────────────────── Tools for rubocop 2 tools ❯ 1\. RuboCop's inspection read-only 2\. RuboCop's autocorrection destructive それでは実際に rubocop_inspection を呼んでみます。オフェンスを意図的に仕込んだ以下のサンプルファイル sample.rb を用意して、検査を呼んでもらいます。 # frozen_string_literal: true def greet ( name ) message = " Hello, " +name+ " ! " puts message end greet( " WEAR " ) 「 sample.rb をRuboCopで検査して」とお願いすると、Claude Codeが rubocop_inspection ツールを呼び、結果を以下のように整形して提示してくれました。 ❯ sample.rb をRuboCopで検査して Called rubocop (ctrl+o to expand) ⏺ sample.rb の検査結果: 9 件の offense が検出されました(すべて自動修正可能)。 ┌─────┬─────────────────────────────┬─────────────────────────────┐ │ 行 │ Cop │ 内容 │ ├─────┼─────────────────────────────┼─────────────────────────────┤ │ 2 │ Layout/SpaceInsideParens │ 括弧内の余計なスペース(2 │ │ │ │ 箇所) │ ├─────┼─────────────────────────────┼─────────────────────────────┤ │ 3 │ Style/StringConcatenation │ \+ 連結より文字列補間を推奨 │ ├─────┼─────────────────────────────┼─────────────────────────────┤ │ 3 │ Style/StringLiterals │ "Hello, " → │ │ │ │ シングルクォート推奨 │ ├─────┼─────────────────────────────┼─────────────────────────────┤ │ 3 │ Style/StringLiterals │ "\!" → シングルクォート推奨 │ ├─────┼─────────────────────────────┼─────────────────────────────┤ │ 3 │ Layout/SpaceAroundOperators │ \+ 演算子の前後にスペース無 │ │ │ │ し(2 箇所) │ ├─────┼─────────────────────────────┼─────────────────────────────┤ │ 4 │ Layout/IndentationConsisten │ インデントが不揃い │ │ │ cy │ │ ├─────┼─────────────────────────────┼─────────────────────────────┤ │ 7 │ Style/StringLiterals │ "WEAR" → │ │ │ │ シングルクォート推奨 │ └─────┴─────────────────────────────┴─────────────────────────────┘ すべて rubocop \-A で自動修正できます。自動修正を実行しますか? 「自動修正を実行しますか?」というフォローアップ付きで、続けて rubocop_autocorrection ツールへの切り替えまで自然に提案してくれているのが分かります(行番号がLSPの0-indexed値のまま表示されているのは、RuboCopが返すLSP Diagnosticの値をClaude Code側で1-indexedに変換せずに表示しているためです)。 このとき裏で rubocop_inspection から返ってきている生のJSONは、紙面の都合で code_actions を省略し、検出された9件のうち代表2件のみ抜粋すると以下のようになっています。 { " files ": [ { " path ": " sample.rb ", " offenses ": [ { " range ": { " start ": { " line ": 2 , " character ": 10 } , " end ": { " line ": 2 , " character ": 11 } } , " severity ": 3 , " code ": " Layout/SpaceInsideParens ", " codeDescription ": { " href ": " https://docs.rubocop.org/rubocop/cops_layout.html#layoutspaceinsideparens " } , " source ": " RuboCop ", " message ": " Layout/SpaceInsideParens: Space inside parentheses detected. ", " data ": { " correctable ": true , " code_actions ": [ /* 省略 */ ] } } , { " range ": { " start ": { " line ": 3 , " character ": 12 } , " end ": { " line ": 3 , " character ": 30 } } , " severity ": 3 , " code ": " Style/StringConcatenation ", " codeDescription ": { " href ": " https://docs.rubocop.org/rubocop/cops_style.html#stylestringconcatenation " } , " source ": " RuboCop ", " message ": " Style/StringConcatenation: Prefer string interpolation to string concatenation. ", " data ": { " correctable ": true , " code_actions ": [ /* 省略 */ ] } } ] } ] , " summary ": { " target_file_count ": 1 , " offense_count ": 9 } } LSPの診断と同じ形式なので、各オフェンスにcop名・該当範囲・cop個別のドキュメントURL・自動修正可否( correctable )まで構造化された形で含まれます。さらに省略した code_actions には実際のautocorrectで挿入する文字列(例: Style/StringConcatenation なら "Hello, #{name}!" への置換)や、 # rubocop:disable を入れて該当行で無効化する案もLSPの WorkspaceEdit 形式で含まれており、エージェントから機械的に扱えるようになっています。 rubocop_autocorrection ツールに切り替えれば、そのまま破壊的に修正をかけることもできます。 WEARのバックエンドではすでにRuboCop+各種pluginを大規模に運用しているので、生成AIによる開発を取り入れる際にも「RuboCopを通っている」という決定的なゲートはそのまま使い続けたい、と日頃から思っていました。本セッションは、まさにその「決定的なツール(RuboCop)」と「確率的なLLM」を rubocop --mcp というかたちで素直に橋渡ししてくれる発表で、聞きながら自分の開発フローに組み込む絵が具体的に浮かびました。 セッション後半で @koic さんが投げかけられていた "What happens when combined?"(決定的なツールと確率的なLLMを組み合わせると何が起きるか)という問いも印象的でした。例えば、現状の .rubocop.yml で無効化しているcopをLLMに一時的に有効化させ、レビュー観点として「いま無効になっているcopをあえて見たらどう見えるか」を提示してもらう、といった使い方は、ツール単体だと出てこない発想です。Streamable HTTPでのSamplingやElicitationまで含めて、まだ「探索が始まったばかり」という締めくくり通り、これからRuboCop×MCPでどんな試みが出てくるかとても楽しみです。 The Journey of Box Building 坂元( @sakam0cchan )です。私からはSatoshi Tagomori( @tagomoris )さんの「 The Journey of Box Building 」というセッションを紹介します。 speakerdeck.com このセッションは、Ruby 4.0で実験的に導入される新機能「Ruby Box」をテーマにしたKeynoteです。Boxの基本概念から、実行中のRubyプログラムの中でBoxを特定する際の難しさ、そしてその実装に至るまでの背景などが語られていました。 私自身が初めてRubyKaigiに参加し、そして初めて聞いたKeynoteがこの内容だったこともあり、これからの3日間がどんなものになるかとてもわくわくしました。そんな思いも込めて、内容を共有します。 Ruby Boxとは何か Ruby Box(RubyKaigi 2025までは「Namespace」と呼ばれていた機能)は、Ruby 4.0で導入された実験的な機能です。 Ruby Boxは、これまでRubyのプロセス全体で共有されていたクラスやモジュール、モンキーパッチなどを、別の空間(Box)として分離し読み込み・実行できる革新的な機能となっています。たとえば、 something.rb に書かれたクラスを、メイン環境とは独立したBoxの中に読み込むイメージです。 # something.rb class Something def hello = " hello from Something " end # main.rb box = Ruby :: Box .new box.require( ' something ' ) s = box:: Something .new p s.hello # => "hello from Something" Boxの中で読み込んだ Something は box::Something としてだけアクセスでき、Boxの外からは見えません。これにより、ライブラリやモンキーパッチをプロセス全体へ漏らさずに使えるのがBoxのコアアイデアです。セッションの中では、以下のような用途がBoxの利用シーンとして挙げられていました。 一部のコードにて有効なモンキーパッチの適用 テスト内でのモック処理 異なるバージョンのライブラリに依存する複数のアプリケーションを、同じプロセス内に同居させる どちらにも同じリクエストを送ることで、異なるバージョンのライブラリでの挙動を比較する このように、Boxは「同じプロセス内での隔離された環境」を提供することで、これまでRubyで難しかったことを可能にする機能として期待されています。 Ruby Boxの仕組み 本セッションでは、Ruby Boxを実現する上で必要不可欠となる「現在、実行中のコードがどのBoxに属しているか」を判定する仕組みが詳細に解説されていました。 Ruby Boxでは、「1つのファイルを、別のBoxで同時に読み込む」といったことが可能です。つまり、ファイル自体は特定のBoxに縛られていないため、プログラムが実行されるたびに、Ruby自身が「自分は今、どのBoxの中で動いているのか?」を動的に特定し続けなければなりません。 Boxの特定・決定のプロセスは、以下のようなステップで行われているとのことでした。 現在実行中のフレームを特定する まず、自分がいま実行しているControl Frameを見つける LOCALな変数スコープを探す そこから、ブロックや例外処理といった非ローカルなものを無視し、ファイル全体、クラス、メソッドといった「LOCAL(局所的)」なスコープを持つフレームまでさかのぼる Boxの情報を引っ張り出す そこで、見つけたLOCALなフレームの中に保存されている情報をたどり、そこに紐付いている「Box情報」を取得する ここで一番興味深かったのは、Control Frameとローカル変数などを管理しているENVとの関係性です。それぞれはRubyを実行する上で、現時点でどのコードが動いているのかを把握するためのものです。Rubyのメモリ上では、Control FrameとENVの2つが両端から内側に向かって伸びていく特殊な構造となっているそうです。 この二つが衝突すると、Stack Overflowが発生するという話をこのセッションで初めて知り、今まであまり意識していなかったRubyの内部構造について、もっとより知って勉強したいと改めて感じました。 Ruby Boxが生まれた歴史 さらに、印象に残っているもう1つのトピックとして、Ruby Boxが生まれた歴史についての話があります。開発者のTagomoriさんがRuby Boxの開発を始めたきっかけは、RubyKaigi 2023で発表された 「Multiverse Ruby」のセッション でした。その内容が、過去に自身が抱えていた課題と重なると感じたことで、一気にモチベーションが高まったそうです。 さらに、その会期中に発表者の方と直接話す機会があり、当初は「Hako」という命名にして函館のRubyKaigiで発表しよう、という話もあったそうで…。そして実際に今、こうして発表に至っている、なんと胸が熱くなる展開でしょうか。 こんなエピソードを通して、「みなさんにも、ふとモチベーションが湧き上がる瞬間があるかもしれない。あなたにとっての『Multiverse Ruby』があるかもしれない。そんなプロセスも含めてRubyKaigiを楽しんでほしい」という言葉が印象的でした。最初のセッションでその一言を聞いたとき、これから始まる3日間のRubyKaigiにとてもわくわくしたあの瞬間を、今でも覚えています。そんな開発の裏エピソードも含め、とても印象的なセッションでした。 おわりに 本セッションは「新機能の紹介」というよりも、どんなふうに実現されているのか、さらにその機能が生まれた瞬間を聴ける発表で、聴いた後の余韻が長く残るキーノートでした。 WEARのバックエンドはRuby on Railsで開発・リプレイスを進めており、monkey patchが絡む箇所のスコープ管理に苦労する場面が今後出てくるかもしれません。Boxのように「実行時の境界」を表現できるようになれば、たとえばライブラリの差し込みやテスト環境固有の上書きを、より見通しよく扱えるようになる可能性を感じました。Ruby Boxはまだ実験的な機能ではありますが、今後も引き続き注目していきたいです! ZOZOブースの紹介 こんにちは。Developer Engagementブロックの wiroha です。ここからはZOZOや各社の協賛ブースの様子を紹介します。 今回は出発当日の朝、管制トラブルの影響で飛行機の運航に乱れが出ている状況からのスタートとなりました。搭乗予定の便も遅延か欠航かの見通しが立たなかったため、早々にキャンセルして新幹線へ切り替えたことが功を奏し、4〜5時間かかったものの比較的早めの時間に函館入りできました。また直前の地震の影響で北海道では配送の遅延が起きていたようですが、幸いブースの荷物は無事に届いており、予定どおり運営できそうでほっとしました。 今回のブースはファッション×AIの2つの新しい体験をご用意しました。ひとつは「 Apps in ChatGPT 」、もうひとつはWEARアプリ内の「 着回し提案 」機能です。 ファッション×AIの新機能を体験しよう! 「 Apps in ChatGPT 」の体験では、技術カンファレンスに合うコーディネートを相談する方や、自分の写真を撮って「もっとおしゃれにするにはどうしたらいい?」と質問する方など、それぞれ自由な対話を楽しんでいる様子が見られました。 「Apps in ChatGPT」を体験中の様子 「 着回し提案 」は特定のアイテムを選択すると、ファッションに特化したAIがユーザーの好みに合わせた着回しを提案します。「知らなかった、便利そう!」という感想を多くいただきました。 「着回し提案」を体験中の様子 いずれかの体験をしていただいた方に、ZOZOらしいノベルティの「 洗濯ネット 」をプレゼントしました。「ちょうど今日洗濯しようと思ってました」「洗濯ネットが欲しくて体験しに来ました」といった声があり、日常的に使えるアイテムとして多くの方に喜んでいただけました! ZOZOらしいノベルティの洗濯ネット 協賛企業ブースのコーディネートまとめ assu_ です。函館へはすんなり辿り着く──はずでした。伊丹空港で突如届く欠航の知らせ、満席の函館便、迫られる決断。滑り込んだ新千歳行き、やけくそで高い寿司を買い、特急北斗で函館へ。家を出てから実に13時間、極寒の函館に到着──虚無の心を救ったのは、函館駅前のハセガワストア。波乱の移動を経て辿り着いた会場には、同じように大変な思いをした人も少なくありませんでした。 そんな会場から、前回の RubyKaigi 2025 同様、素敵なコーディネートの協賛企業の皆様を撮影させていただきました! デザインスポンサーのGaji-Laboさん Committer限定のMA-1は、希少性と刺繍の2026デザインがとてもクール。 北海道らしいイラストをあしらったフーディーの note さん。 パンダをモノクロにすると北海道らしくなったという GMO Flatt Security さん。 CM でおなじみのキャラクターにより個性的なデザインの ファインディ さん。 シンプル胸元とカタカナ背中の対比が良い、ネットプロテクションズさん。 襟シャツに絞り付きの裾、男性も女性も着こなし上手だったギフティさん。 ロゴのラインが今っぽい、視認性が高くセンスの良いWEDさん。 誇り高きデザインにより、長い Ruby歴 を表しているKOMOJUさん。 桜が満開の函館に一番ぴったりなカラーと、法被で華やかなwithさん。 Wellness Sponsorらしいブラックで “ W ”orkoutしやすそうなhacomonoさん。 北海道・函館らしいデザインや、変化する気温に対応しやすそうな形が目立った回でしたね。お忙しい中ご協力いただいたブースの皆さん、本当にありがとうございました! RubyKaigi 2026 アフターイベント〜初参加LT・スポンサー4社のパネル〜を開催します RubyKaigi 2026 アフターイベント〜初参加LT・スポンサー4社のパネル〜 5月13日(水)にRubyKaigi 2026スポンサー企業の株式会社ZOZO、株式会社リブセンス、株式会社TOKIUM、株式会社マイベストでRubyKaigi 2026のアフターイベント「 RubyKaigi 2026 アフターイベント〜初参加LT・スポンサー4社のパネル〜 」を開催します。 RubyKaigi 2026に参加した方も、参加できなかった方も、ぜひお気軽にご参加ください! mybest.connpass.com おわりに 世界中のRubyistが函館の地に集まりました! ZOZOは毎年RubyKaigiに協賛し、ブースを出展しており、今年も多くの方々との交流を通じて有意義な時間を過ごすことができました。実行委員会の皆様、そして温かく迎えてくださった函館市の皆様に感謝申し上げます。来年も再び素晴らしい時間を共有できることを楽しみにしております! 技術カンファレンスでは恒例のスポンサーパネルへのサイン! RubyKaigi 2027 #rubykaigi 🗓️ 14..16 Apr 2027 🥭 Miyazaki, Japan — RubyKaigi (@rubykaigi) 2026年4月24日 そして、次回の開催地は私たちZOZOの宮崎オフィスがある宮崎県です。宮崎在住エンジニアも数名在籍しているので、地元を生かしたイベントなどができると良いですね。それではまた来年、RubyKaigi 2027でお会いしましょう。現場からは以上です! ZOZOでは、来年のRubyKaigi 2027を一緒に盛り上げるエンジニアを募集しています。ご興味のある方は以下のリンクからご応募ください。 corp.zozo.com
Linuxは、多くの企業システム、クラウド環境、コンテナ基盤で使われています。 そのため、Linuxカーネルに深刻な脆弱性が見つかると、影響はとても大きくなります。 Elastic Security Labs は、Linuxカーネルの権限昇格脆弱性である Copy Fail (CVE-2026-31431) 、Copy Fail 2、そして DirtyFrag を分析しました。これらは、Linuxの page cache に関係するバグを悪用し、通常ユーザーから root権限 を取得できる可能性がある攻撃です。 特に Copy Fail (CVE-2026-31431) は実際の攻撃で悪用されたことが報告されており、米国CISAの Known Exploited Vulnerabilities (KEV) カタログ にも追加されています。KEVカタログに載るということは、「机上の脆弱性」ではなく「現実に攻撃で使われている脆弱性」であることを意味します。米国の連邦機関は期限内のパッチ適用が義務付けられるレベルであり、民間企業にとっても優先対応すべき強いシグナルです。 この記事では、この攻撃をセキュリティ初心者にもわかるように整理しながら、Elastic Securityがどのように脅威を分析し、検知につなげているのか、そして Elasticが公開している検知ルール を紹介します。 目次 まず、何が危険なのか? page cache とは何か? page cache corruption とは? Copy Fail は何をするのか? DirtyFrag は何が違うのか? ⚠️ ここが最重要:Copy Fail のパッチだけでは不十分 なぜ Elastic の分析が重要なのか? Elastic が公開した検知ルール5本 1. Potential Copy Fail (CVE-2026-31431) Exploitation via AF_ALG Socket 2. Suspicious SUID Binary Execution 3. Suspicious Kernel Feature Activity 4. Namespace Manipulation Using Unshare 5. Privilege Escalation via SUID/SGID 5本のルールがどう連携するか Elastic の強み:すべての検知ルールが GitHub で公開されている これがなぜ重要なのか? 日本のユーザーにとっての意味 ビジネス視点でなぜ重要なのか? まとめ:Elastic Security は「攻撃の形」ではなく「攻撃の動き」を見る 参考リンク まず、何が危険なのか? 今回のポイントは、攻撃者がLinux上で root権限 を取得できる可能性があることです。 rootとは、Linuxにおける最も強い権限を持つユーザーです。 たとえるなら、ビル全体のマスターキーを持つ管理者のような存在です。 通常ユーザーは、自分の部屋や許可された場所にしか入れません。 しかしrootは、システム全体の設定変更、ファイルの読み書き、プロセスの停止、ユーザー作成など、多くの操作ができます。 つまり、攻撃者がroot権限を取ると、次のようなことが可能になります。 機密ファイルを読む セキュリティツールを止める マルウェアを設置する ログを改ざんする 他のシステムへ侵入する足がかりを作る これは、単なる「1台のLinuxサーバーの問題」ではありません。 企業のクラウド環境、コンテナ基盤、業務システム全体に影響する可能性があります。 page cache とは何か? 今回の攻撃を理解するために、まず page cache を理解する必要があります。 page cacheとは、Linuxがファイルアクセスを速くするために使うメモリ上の一時的な保管場所です。 たとえば、図書館をイメージしてください。 本棚にある本が「ディスク上のファイル」だとします。 でも、よく読まれる本を毎回本棚まで取りに行くのは面倒です。 そこで図書館員は、よく使う本のコピーを机の上に置いておきます。 この「机の上のコピー」が、Linuxでいう page cache です。 たとえ Linux 本棚の本 ディスク上のファイル 机の上のコピー page cache 図書館員 Linuxカーネル 本を読む人 アプリケーション 通常、page cacheは便利な仕組みです。 ファイルを毎回ディスクから読むより、メモリから読んだ方が速いからです。 しかし、今回のような脆弱性では、この「メモリ上のコピー」が悪用されます。 page cache corruption とは? page cache corruption とは、簡単に言うと、Linuxが信頼しているメモリ上のファイルコピーを不正に書き換えることです。 重要なのは、攻撃者が必ずしもディスク上の本物のファイルを書き換えるわけではない、という点です。 本物のファイルは変わっていないように見えます。 しかし、Linuxが実際に使うメモリ上のコピーだけが壊されている可能性があります。 これは非常に厄介です。 なぜなら、ファイルの改ざんチェックをしても、ディスク上のファイルは正常に見えることがあるからです。 一方で、システムは壊されたpage cacheの内容を使ってしまう可能性があります。 たとえるなら、会社の正式な契約書は金庫の中で無事なのに、担当者が机の上に置いていたコピーだけがこっそり書き換えられている状態です。 担当者がそのコピーを信じて処理を進めると、間違った判断につながります。 Copy Fail は何をするのか? Elasticの説明によると、Copy Fail は Linuxカーネルの暗号処理(authencesn テンプレート)に関係するロジックバグです。AF_ALG と splice() という Linux の正規機能を組み合わせることで、読み取り可能なファイルのpage cacheに対して、制御された4バイトの書き込みを行えると説明されています。 ここで重要なのは、これが setuidバイナリ に対して使われるという点です。 setuid バイナリとは 実行したユーザーではなく、ファイルの所有者の権限で動く特殊なプログラムです。 たとえば /usr/bin/su は所有者がrootで、setuid が設定されているため、認証処理などの一部の処理をroot権限で実行できます。もちろん、通常はパスワード確認などの認証があるため、誰でも自由にrootになれるわけではありません。 しかし、この「root権限で動く部分」こそが攻撃者の標的になります。Copy Fail のような攻撃では、ディスク上のファイルを直接書き換えるのではなく、page cache上のバイナリ内容を壊すことで、root権限で実行される処理を悪用しようとします。 実際に Copy Fail では、/usr/bin/su のようなsetuidバイナリのメモリ上の見え方を壊し、 ディスク上のファイルを変更せずに権限昇格 につなげることができます。公開されている攻撃コードは、わずか732バイトのPythonスクリプトで、Ubuntu、Amazon Linux、RHEL、SUSE といった主要ディストリビューションで動作します。 ここで重要なのは、攻撃者が「怪しいマルウェア」だけを使っているわけではないことです。 AF_ALG も splice() も、Linuxに存在する正規の仕組みです。 つまり攻撃は、完全に外部から見て明らかに怪しい動作だけで成り立っているわけではありません。 正規のLinux機能を組み合わせて、カーネルの細かいバグを突いています。 これが、検知を難しくします。 DirtyFrag は何が違うのか? DirtyFragも、page cache corruption を悪用する Linuxカーネルの権限昇格攻撃です。 基本の考え方は Copy Fail と似ていますが、 攻撃経路がネットワークスタックに広がっている 点が大きく異なります。 Elasticのブログによると、DirtyFragには2つの経路があります。 経路 使われる仕組み 攻撃対象 結果 ESPパス AF_NETLINK 経由のXFRM SA /usr/bin/su suを最小限のroot shell ELFで上書き RxRPCパス(フォールバック) AF_RXRPC + pcbc(fcrypt) /etc/passwd rootのパスワードフィールドをクリア /etc/passwd のrootパスワードフィールドがクリアされると、環境によってはパスワードなしでrootとして認証が通ってしまう可能性があります。 実際の挙動は、PAM や SSH の設定、shadow ファイルの運用状況によって変わります。しかし、いずれにしても、root認証の前提を壊す重大な改ざんであることに変わりはありません。 さらに、両方の経路ともに unshare(CLONE_NEWUSER | CLONE_NEWNET) を使って namespace capability を取得する前段が必要です。これは、後述する検知ロジックで重要なポイントになります。 ⚠️ ここが最重要:Copy Fail のパッチだけでは不十分 Elasticのブログが警告している最も重要なポイントは次のことです。 DirtyFrag は algif_aead モジュールに依存しません。 つまり、Copy Fail の緩和策(algif_aead を無効化する)だけを適用したシステムは、依然としてDirtyFragに脆弱なままです。 「Copy Fail対策はやったから安心」という思い込みが、最も危険な状態を生みます。 両方の脆弱性に対して、 それぞれ独立した対策が必要 です。 なぜ Elastic の分析が重要なのか? ここからが、Elastic Securityを理解するうえで大事なポイントです。 Elasticは、単に「特定の攻撃コードを見つけましょう」という見方だけをしていません。 セキュリティ研究者は、新しい脆弱性が見つかると、しばしば PoC(Proof of Concept) と呼ばれる実証コードを公開します。これは「この攻撃が本当に可能であることを示すデモコード」であり、防御側が脆弱性を理解し、対策を検証するために使われます。 しかし、攻撃者はこの実証コードをそのままの形で使うとは限りません。 Pythonで書かれたコードを、GoやRustやCに書き換えることもできます。 ファイル名やプロセス名を変えることもできます。実行方法を少し変えることもできます。 実際、Copy Fail はすでに Python / Go / Rust / C / Metasploit など、複数の言語・フレームワークで実装が公開されており、DirtyFrag も C言語版の実装が公開されています。 そのため、 特定の攻撃コードの見た目だけを検知していると、少し変えられただけで見逃す可能性があります 。 Elasticが重視しているのは、攻撃の primitive と behavior です。 primitive とは、攻撃を構成する小さな技術的な部品のことです。 たとえば、ビルへの侵入で考えると、攻撃全体は「不正侵入」です。 その中のprimitiveは、次のような小さな行動です。 鍵をこじ開ける 監視カメラを避ける 裏口を使う 管理室に入る Linux攻撃で言えば、primitiveは次のようなものです。 AF_ALG を使う splice() を使う page cache を壊す setuidバイナリを悪用する unshare() でnamespaceを作る Elasticは、攻撃コードそのものだけでなく、こうした攻撃の部品や行動パターンを見ようとしています。 これは、現代のセキュリティ検知において非常に重要です。 Elastic が公開した検知ルール5本 今回 Elastic Security Labs は、Copy Fail / DirtyFrag に対応する検知ルールを公開しました。 ここで重要なのは、 これらのルールはすべて GitHub で誰でも見られる ということです(後述)。 以下、5本のルールがそれぞれ「何を検知するか」を簡潔に紹介します。 1. Potential Copy Fail (CVE-2026-31431) Exploitation via AF_ALG Socket 何を検知するか: 非rootユーザーが AF_ALG ソケット(暗号処理用の特殊なソケット)と splice() を組み合わせて使い、その後 root 権限のプロセス実行やシェル起動につながる流れ。 攻撃のどこで効くか: Copy Fail の最も核心的なプリミティブを直接捉えます。 前提条件: このルールを有効に活用するには、Linux 上で auditd 系のログを Elastic に取り込んでいる必要があります。具体的には、Elastic Agent の Auditd Manager integration や Auditbeat の設定が必要です。これらがない環境では、socket や splice のような低レベルな syscall の動きは見えません。 🔗 GitHubでルールの実物を見る 2. Suspicious SUID Binary Execution 何を検知するか: su、sudo、pkexec、passwd などのSUIDバイナリが、不審な親プロセス(PythonやRubyなどのスクリプトランタイム、/tmp や /dev/shm といったユーザー書き込み可能パスからの実行)から、最小限の引数で呼び出されるパターン。 攻撃のどこで効くか: Copy Fail / DirtyFrag の両方の 最終段階 (root権限取得の瞬間)を捉えます。auditd が入っていない環境でも、プロセス実行イベントだけで動作するため、適用範囲が広いのが特徴です。 🔗 GitHubでルールの実物を見る 3. Suspicious Kernel Feature Activity 何を検知するか: sysctl などによるカーネル機能の不審な操作。攻撃者が防御機構を無効化したり、カーネル動作を変更したりする動きを捉えます。 位置づけ: このルールは Copy Fail / DirtyFrag 専用というより、攻撃者がカーネル機能を不審に操作する動きを広く見るための補助的な検知です。今回のようなカーネル悪用の文脈でも、関連する不審な操作を見つけるための追加レイヤーとして役立ちます。 🔗 GitHubでルールの実物を見る 4. Namespace Manipulation Using Unshare 何を検知するか: unshare コマンドや syscall によるユーザーネームスペース(特に CLONE_NEWUSER | CLONE_NEWNET)の作成と、その直後の root プロセス実行・setuid(0) の相関。 攻撃のどこで効くか: DirtyFrag 固有の前段 を捉えます。DirtyFrag は namespace の取得が必須なので、ここを潰すと攻撃チェーン全体が成立しなくなります。 前提条件: このルールも、syscall レベルの検知部分は auditd 系のログが必要です。プロセス実行イベントの部分は Elastic Agent / Endpoint で取得できます。 🔗 GitHubでルールの実物を見る 5. Privilege Escalation via SUID/SGID 何を検知するか: SUID/SGIDバイナリを悪用した権限昇格全般のパターン。Copy Fail / DirtyFrag に限らず、類似の権限昇格手法を広くカバーします。 攻撃のどこで効くか: 汎用的な権限昇格の最終段階。Copy Fail / DirtyFrag の派生や、まだ公開されていない類似手法にも備える保険的なルールです。 🔗 GitHubでルールの実物を見る 5本のルールがどう連携するか これらのルールは、それぞれ独立して動きますが、 攻撃の異なる段階を多層的にカバーする設計 になっています。 攻撃者が1つの段階を回避しても、別の段階で検知できる 多層防御(defense in depth) の考え方です。 Elastic の強み:すべての検知ルールが GitHub で公開されている ここで、Elastic Security の重要な特徴をお伝えします。 Elastic は、商用製品の検知ルールをすべて GitHub で公開しています。 リポジトリはこちらです: 🔗 elastic/detection-rules このリポジトリには、Elastic Security で使われる検知ルールが TOML 形式で格納されており、 誰でも自由に閲覧・Fork・コメント・Pull Request 可能 です。 これがなぜ重要なのか? セキュリティ運用において、検知ルールの中身がわからないことは、いくつもの問題を引き起こします。 検知ルールが見られない場合 検知ルールが公開されている場合 アラートが出たが、なぜ発火したかわからない ルールのロジックを読んで理由を理解できる 誤検知が出ても、チューニングできない 条件を読んで、自社環境向けに例外を追加できる 「ベンダーを信じるしかない」状態 自分でレビューして納得できる 検知漏れがあっても、原因がわからない ロジックの穴を発見し、改善提案できる コミュニティ知見が共有されない OSSとしてコミュニティに還元できる なお、検知ルールを公開しているベンダーは Elastic だけではありません。Microsoft Sentinel も Analytics rules を GitHub で公開しており、透明性の高いアプローチを取っています。一方、多くの商用 EDR/SIEM 製品では検知ロジックが非公開で、ユーザーがルールの中身を確認できないことも珍しくありません。Elastic は早い段階からこの公開方針を一貫して続けている点が特徴です。 日本のユーザーにとっての意味 日本では、Elastic を完全に理解しているエンジニアはまだ多くありません。 だからこそ、 ルールがオープンソースとして公開されている ことの価値は大きいです。 英語のブログを完全に理解できなくても、 TOMLファイルを読めば検知ロジックがわかる 社内SOCのナレッジとして ルールを写経・改造して学べる 自社環境特有の誤検知に対して 自分で例外条件を追加できる 日本語コミュニティで ルールの解釈を議論できる ビジネス視点でなぜ重要なのか? この話は、セキュリティ研究者だけのものではありません。 企業にとって重要なのは、次の3つです。 1つ目は、被害の早期発見です。 root権限を取られると、攻撃者はより深くシステムに入り込めます。早く気づければ、被害を小さくできます。 2つ目は、調査時間の短縮です。 SOCやセキュリティ担当者は、毎日多くのアラートを見ています。Elastic Securityのように、攻撃の流れを見せられる仕組みがあると、「これは何が起きているのか」を早く理解できます。 3つ目は、未知・変種への対応力です。 攻撃者は攻撃コードを書き換えます。ツール名も変えます。実行方法も変えます。 しかし、攻撃に必要な基本行動は大きく変わりにくいです。 だからこそ、Elasticが重視している「ふるまい検知」は、ビジネスにとっても価値があります。 まとめ:Elastic Security は「攻撃の形」ではなく「攻撃の動き」を見る Copy Fail や DirtyFrag は、Linuxカーネルの細かいバグを悪用する高度な攻撃です。 しかし、初心者向けに一言でまとめるなら、こう言えます。 Linuxが高速化のために使っている page cache を悪用し、メモリ上のファイル内容を壊すことで、通常ユーザーから root権限を取る攻撃です。 そして、Elastic Security Labs の重要な貢献は、これを単なる脆弱性情報として紹介するだけでなく、 実際の検知ルールに落とし込み、GitHub で公開している 点です。 特定の攻撃コードだけを見るのではなく、攻撃に必要な primitive や behavior を見る。 そして、そのロジックをオープンにすることで、コミュニティ全体の防御力を底上げする。 これは、現代のセキュリティ運用においてとても重要な考え方です。 攻撃者はコードの見た目を変えられます。 しかし、root権限を取るために必要な行動の流れは、完全には隠しにくいです。 Elastic Security は、その流れをデータから見つけるためのプラットフォームです。 そして、その検知ロジックを オープンに、透明に、コミュニティと共に進化させている のが、Elastic の大きな強みです。 参考リンク Elastic Security Labs 原文ブログ: Copy Fail and DirtyFrag: Linux Page Cache Bugs in the Wild Elastic の検知ルールリポジトリ: elastic/detection-rules (GitHub) CISA Known Exploited Vulnerabilities Catalog: CISA KEV この記事は、Elastic Security Labs が公開した英語ブログ「Copy Fail and DirtyFrag: Linux Page Cache Bugs in the Wild」をもとに、日本のElastic利用者向けに整理・補足したものです。 The post Copy Fail / DirtyFrag を検知する:Linux カーネルの page cache 攻撃と5つの検知ルール first appeared on Elastic Portal .
動画
該当するコンテンツが見つかりませんでした














