
プログラミング
イベント
マガジン
技術ブログ
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
本記事は 2026 年 5 月 7 日に公開された “ Full-text, exact-match, range, and hybrid search on Amazon ElastiCache ” を翻訳したものです。 訳者註: 本記事の全文検索は、現時点では言語オプションとして english のみをサポートしており、日本語テキストはスペースや句読点で区切られた単位でインデックスされます。日本語を全文検索する場合は、事前に形態素解析で語ごとにスペースで区切ったテキストをインデックスし、検索クエリも同じ形で渡すようにしてください。 Amazon ElastiCache は、別個の検索サービスを使用することなく、キャッシュ内で直接リアルタイムの全文検索、完全一致検索、数値範囲検索、ハイブリッド検索をサポートするようになりました。低レイテンシーで動的データに対するスケーラブルな検索を必要とするワークロードに対して、アプリケーションはマイクロ秒単位のレイテンシーと毎秒最大数百万回の検索操作のスループットでテラバイト規模のデータを検索できます。これらの新しい検索機能により、開発者は ElastiCache に既に保存されているデータを、単純なキーバリュー検索を超えた属性で柔軟にクエリできるようになります。 完全一致検索は、商品名、カテゴリ、ユーザー ID、注文番号など、テキスト、タグ、数値属性にわたる正確な値の一致によってドキュメントを取得します。数値範囲検索は、価格のしきい値、日付範囲、取引金額などの属性によってドキュメントをフィルタリングします。完全一致に加えて、全文検索はテキスト属性に対して、オートコンプリート (入力補完) 用のプレフィックスマッチング、タイプミス許容のためのファジーマッチング、複数語検索のための近接マッチングを実行します。これらの検索タイプをベクトル類似度と単一のハイブリッドクエリで組み合わせることができ、正確な用語と意味的な意図の両方を捉えることで、いずれかの手法を単独で使用するよりも関連性の高い結果を提供します。ベクトルワークロードにおいて、ElastiCache for Valkey は AWS 上の主要なベクトルデータベースの中で、95% 以上の再現率で最低レイテンシかつ最高スループットのベクトル検索、そして最高の価格性能比を実現します。 これらの検索機能は、ElastiCache version 9.0 for Valkey で利用可能であり、リアルタイム分析やレポーティング向けのサーバーサイド集計機能も併せて提供されています ( Amazon ElastiCache での集計機能のお知らせ を参照)。ElastiCache version 9.0 for Valkey では、個々のフィールドに対するきめ細かい TTL 制御を可能にするハッシュフィールドの有効期限機能や、最大 40% 向上したパイプライン化スループットも導入されています。リリースの詳細については、 Amazon ElastiCache 向け Valkey 9.0 のお知らせ をご覧ください。 この記事では、新しい検索機能を順を追って紹介し、それらがどのように連携するかを示しながら、検索およびレコメンデーションエンジンをゼロから構築していきます。 複数のアプリケーションにわたる強力なリアルタイム検索を実現 お客様からは、アプリケーションがスケールするにつれて、検索ワークフローがビジネスに求められるスループットをサポートしながら、ユーザーが期待する低レイテンシーの体験を維持する必要があるとの声が寄せられています。例えば、決済プラットフォーム、ストリーミングプラットフォーム、オンライン小売業者などは、何百万ものドキュメントを ElastiCache に保存し、マイクロ秒のレイテンシーでメタデータ属性によってデータを取得する必要があります。さらに、ワークロードが進化するにつれて、すでに ElastiCache に保存されているデータに対して新しいユースケースをサポートする豊富な検索クエリが必要だというお客様の声もあります。例えば、アプリケーションはデバイスタイプ、セッション状態、ユーザーアクティビティなどのユーザーおよびセッションコンテキストを ElastiCache に保存し、低レイテンシーの体験を提供することがよくあります。ワークロードが進化するにつれて、お客様はその同じデータをレコメンデーションシステムの基盤として活用したいと考えており、そのためにはこれらの属性をまたいだ検索が必要になります。 ElastiCache では、マイクロ秒単位の低レイテンシーと毎秒数百万クエリ (QPS) のスループットでデータを検索・取得するためのさまざまな方法が提供されるようになりました。書き込みが完了するとすぐにデータが検索可能になるため、アプリケーションは常に最新の結果をクエリできます。これらの機能は、カタログ検索、レコメンデーションエンジン、エージェント型メモリ、リアルタイムのリーダーボード、セッション検索などのユースケースを支えます。 カタログ検索: オンライン小売業者やストリーミングプラットフォームは、顧客が大規模なカタログから商品を見つけられるような検索体験を構築しています。これらのプラットフォームでは、商品名や説明文に対するテキスト検索と、ブランド、カテゴリ、価格、評価によるフィルタを単一のクエリで組み合わせることで、ファセットブラウジング体験を提供できます。プレフィックスマッチングは、ユーザーが入力するにつれて候補を読み込むタイプアヘッド検索を実現し、マイクロ秒単位で結果を返すため、即座に反応するような体験を提供します。さらに、ファジーマッチングを活用したタイプミスに強い検索を組み合わせることで、スペルミスを自動的に処理し、検索体験をより堅牢にできます。ファジーマッチングは完全一致検索よりも計算コストが高いため、ElastiCache のようなインメモリ検索エンジン上で実行することで、高速で応答性の高い体験を維持できます。 レコメンデーションエンジン: カタログが数百万アイテムに拡大するにつれて、ユーザーはデジタルプラットフォームに対して、関連性の高いコンテンツや商品を素早く表示するパーソナライズされた閲覧体験を期待しています。最新のレコメンデーションシステムは、ユーザーとアイテムをベクトル埋め込みとしてエンコードします。これらのシステムは、ベクトル検索と名前、説明、カテゴリ、在庫状況、価格帯などのフィルターを組み合わせて、数百万のアイテムからレコメンデーション候補を取得します。ハイブリッド検索は、テキスト、タグ、数値フィルターをベクトル類似度と単一のクエリで組み合わせることでこれをサポートし、取得される候補は意味的に関連性があり、ビジネス上の制約も満たします。商品ページでは、同じカテゴリと価格帯にフィルタリングしてから埋め込みの類似度でランク付けすることで、「類似アイテム」を表示できます。これを拡張して、行動履歴 (閲覧したアイテムの埋め込みの平均プーリング、アテンションベースのモデル、シーケンシャルモデルなどの手法を使用) からユーザー埋め込みを構築し、それをベクトルクエリとして渡すことで、学習した嗜好に基づいて結果をランク付けするパーソナライズされたレコメンデーションを実現できます。 エージェントメモリ: エージェントメモリは、エージェントが過去のやり取りから学習することで、会話履歴全体を再生することなく応答の関連性を向上させ、トークンコストを削減します。エージェントメモリシステムは、スコープ属性 (ユーザー、エージェント、セッション) と現在のやり取りに対する意味的関連性によってメモリを保存・取得します。ハイブリッド検索を使用することで、これらのシステムはスコープやテキストフィルターとベクトル類似性を 1 つのクエリで組み合わせます。エージェントメモリはライブの会話パス上にあるため、書き込み後すぐに読み取れる可視性が求められ、新しく保存された事実が即座に取得可能である必要があり、新しいメモリの取得と統合のために高い同時読み書き性能が求められます。ElastiCache は書き込み時にメモリを同期的にインデックス化し、マルチスレッディングを活用し、AWS 上の主要なベクトルデータベースの中で最高のスループットをマイクロ秒レベルのレイテンシーで提供します。ElastiCache と Mem0 を使用したステップバイステップの実装については、 Build persistent memory for agentic AI applications with Mem0 Open Source, ElastiCache for Valkey, and Amazon Neptune Analytics をご覧ください。 ElastiCache for Valkey は、セルフマネージドのメモリレイヤーを構築したい場合や、低レイテンシでカスタマイズ可能なインメモリストアが必要な場合に適しています。フルマネージドのアプローチをお好みの場合は、 Amazon Bedrock AgentCore Memory を使用してメモリを管理することもできます。 金融アプリケーションとリーダーボード: 取引プラットフォームやゲームアプリケーションでは、取引金額、タイムスタンプ、リスクスコア、プレイヤーランキングといった数値属性を持つドキュメントを保存し、低レイテンシーで取得する必要があります。ElastiCache の数値範囲クエリは、これらの属性に対する高速な検索をサポートし、時間枠、金額のしきい値、スコア帯によるフィルタリングを可能にします。ゲームアプリケーションでは、スコアの更新を即座に反映するリアルタイムなリーダーボードを維持でき、「自分の地域のトップ 100 プレイヤー」のような範囲クエリにも対応できます。 ユーザーおよびセッション管理: 各業界のアプリケーションは、セッション管理のためにセッション ID、デバイスタイプ、ユーザーハンドルといった構造化属性をキャッシュに保存しています。これらのアプリケーションは、ユーザーがログインするとセッションデータをキャッシュに書き込み、セッションのライフサイクルを通じて更新するため、即時に検索可能な高速書き込みが求められます。ElastiCache は更新を同期的にインデックス化するため、セッション属性に対する検索は遅延なく最新の状態を反映します。完全一致検索により、数百万のドキュメントから正確な識別子に基づいてアクティブなセッションや権限をサブミリ秒のレイテンシーで特定できます。 ElastiCache を使った検索・レコメンデーションエンジンの構築 これらの検索タイプを組み合わせて実演するため、エレクトロニクス、美容、家庭用品など何百万もの製品を販売する e コマースプラットフォーム AnyCompany 向けの検索およびレコメンデーションエンジンを構築します。AnyCompany は、買い物客がキーワードで製品を見つけ、ブランドや価格帯などのフィルターで絞り込み、類似性を通じて関連商品を発見できる検索体験を求めています。AnyCompany は 100 万を超える商品カタログを ElastiCache にハッシュベースのドキュメントとして保存しています (この例では、実際のタイトル、説明、ブランドを含む Amazon ESCI データセット から派生したもの)。次のコードは、このデータに対して 5 つのクエリパターンを構築します: 入力補完検索、全文一致、タイポに強いマッチング、フィルター付きブラウジング、そして類似商品のレコメンデーションです。 前提条件 この記事の例では、 valkey-py クライアントライブラリと Python を使用しています。手順を実行するには、以下が必要です (所要時間の目安: 30 分): AWS アカウント と AWS Command Line Interface (AWS CLI) ElastiCache レプリケーショングループを作成する権限を持つ AWS IAM ロール Amazon ElastiCache クラスターと同じ VPC 内にある Amazon EC2 インスタンス (または Amazon ElastiCache に接続可能 な任意のアプリケーション) Python 3.9 以降および valkey-py バージョン 6.1.1 以降 (pip install valkey) この記事の完全なサンプルコードは、ElastiCache samples GitHub リポジトリで入手できます。 ElastiCache for Valkey クラスターのセットアップ ElastiCache の検索用クラスターは、AWS Management Console または AWS CLI を使用して作成できます。以下の例では CLI を使用しています。検索機能は ElastiCache for Valkey バージョン 9.0 以降で利用可能です。 aws elasticache create-replication-group \ --replication-group-id AnyCompany-cache \ --replication-group-description "AnyCompany Valkey cluster" \ --engine valkey \ --engine-version 9.0 \ --transit-encryption-enabled \ --cache-node-type cache.r7g.large \ --replicas-per-node-group 0 インデックスの作成とデータのロード 商品データを検索可能にするため、 products_vec_index というインデックスを作成します。タイトルと説明は、キーワード、前方一致、あいまい検索をサポートする全文検索可能な属性としてインデックス化されます。ブランドと色は、絞り込み検索のために完全一致タグとしてインデックス化されます。価格、評価、在庫は、範囲クエリやソートのためにソート可能な数値属性としてインデックス化されます。 embedding は、セマンティック類似検索やレコメンデーションのためにベクトル属性としてインデックス化されます。 import gzip import json import struct import urllib.request import valkey from valkey.commands.search.field import TextField, TagField, NumericField, VectorField from valkey.commands.search.indexDefinition import IndexDefinition, IndexType # : Insert your ElastiCache cluster's endpoint VALKEY_HOST = "placeholder_cluster.cnxa6h.clustercfg.use1.cache.amazonaws.com" client = valkey.Valkey(host=VALKEY_HOST, port=6379, decode_responses=False, ssl=True, ssl_cert_reqs="required") # Create the search index with text, tag, numeric, and vector fields try: client.execute_command("FT.DROPINDEX", "products_vec_index") except: pass client.ft("products_vec_index").create_index( fields=[ TextField("title"), TextField("description"), TagField("brand", separator=","), TagField("color", separator=","), NumericField("price"), NumericField("rating"), NumericField("stock"), VectorField("embedding", "FLAT", { "TYPE": "FLOAT32", "DIM": 64, "DISTANCE_METRIC": "COSINE"})], definition=IndexDefinition(prefix=["pv:"], index_type=IndexType.HASH)) ElastiCache ストアに商品データセットを投入します。このデータセットは 130 万件の商品のサブセットで、タイトル、説明、ブランド、および Amazon ESCI Shopping Queries データセットから導出された事前計算済みの 64 次元エンベディングを含む 13.7 万件の商品で構成されています。サンプルリポジトリをクローンし、ロードスクリプトを実行してください: git clone https://github.com/aws-samples/amazon-elasticache-samples.git cd amazon-elasticache-samples/blogs/elasticache-valkey/fts-benchmark # <入力が必要>: VALKEY_HOST 変数をクラスターのエンドポイントで更新して実行: python load_products_blog.py タイプアヘッド検索 (先行入力検索) インデックスとデータが準備できたら、AnyCompany はインデックスに対して実行され、一致するドキュメントを返す FT.SEARCH クエリを使って検索エンジンを構築できます。ユーザーが検索バーに入力すると、アプリケーションはプレフィックスクエリを送信し、リアルタイムで候補を表示します。 from valkey.commands.search.query import Query results = client.ft("products_vec_index").search( Query("wire*").return_fields("title").paging(0, 5)) # User has typed "wire" - prefix match shows suggestions # Output: # [{'title': 'xyz Kids Wireless Headphones'}, # ... # ... # {'title': 'Santas Wire Christmas Lighting Storage Bag'}] フレーズマッチング ユーザーが Enter キーを押すと、アプリケーションはタイトルと説明に対して全文検索を実行します。SLOP は、単語同士がどれだけ離れていても一致と見なすかを制御し、単語がより近接している結果ほど上位にランク付けされます。 # User submits "wireless headphones" # SLOP 2 allows up to 2 words between terms results = client.ft("products_vec_index").search( Query("wireless headphones") .slop(2) .return_fields("title", "brand", "price").paging(0, 5)) # Output: # [{'title': 'xyz Studio3 Wireless Headphones - Gray (Renewed)', # 'brand': 'xyz', 'price': '1928.28'}, # ... # {'title': 'xyz TUNE 220TWS - True Wireless in-Ear Headphone - Blue', # 'brand': 'xyz', 'price': '1121.23'}] タイプミスを許容するマッチング クエリが結果を返さない場合、アプリケーションはタイプミスを修正するためにあいまい一致 (fuzzy matching) で再試行します。あいまい一致は編集距離を計算するためコストが高いので、デフォルトとしてではなくフォールバックとして使うのが最適です。 # Retry with fuzzy matching for "wireles headphoens" results = client.ft("products_vec_index").search( Query("%wireles% %headphoens%") .return_fields("title", "brand", "price").paging(0, 5)) # Output: # [{'title': 'xyz Comfort 35 Wireless Headphones, Noise Cancelling - Silver (Renewed)', # 'brand': 'xyz', 'price': '1811.75'}, # ... # ... # {'title': 'xyz SoundSport Wireless Headphones, Black + Charging Case', # 'brand': 'xyz', 'price': '568.47'}] フィルタリングによる閲覧 買い物客が商品を検索してフィルターを適用すると、アプリケーションはテキスト検索とタグおよび数値フィルターを単一のクエリで組み合わせます。 # ユーザーが「headphones」を検索し、価格 $50-$150、評価 4.0 以上でフィルタリング results = client.ft("products_vec_index").search( Query("@title:headphones @price:[50 150] @rating:[4.0 5.0]") .return_fields("title", "brand", "price", "rating") .paging(0, 5)) # 出力: # [{'title': 'xyz WH1000XM3 Bluetooth Wireless Noise Canceling Headphones', # 'brand': 'xyz', 'price': '102.29', 'rating': '4.8'}, # ... # ... # {'title': 'Bluetooth Earbuds xyz SoundLink .. in Ear Headphones', # 'brand': 'xyz', 'price': '125.45', 'rating': '4.5'}] 類似商品のレコメンデーション 「類似商品」のレコメンデーションを実現するために、AnyCompany はテキストフィルターを使ったハイブリッド検索で結果を該当する商品タイプに絞り込み、ベクトル検索で表示中の商品との類似度に基づいてフィルター済みの結果をランク付けしています。 # Get the embedding of the product the user is currently viewing # for example - "Kids Headphones with Microphone 2 Pack" product_embedding = client.hget("pv:B0825SSTMN", "embedding") # Hybrid: text pre-filter "headphones" + vector KNN for similarity ranking results = client.ft("products_vec_index").search( Query("@title:headphones =>[KNN 5 @embedding $vec AS score]") .return_fields("title", "brand", "price", "score") .dialect(2), query_params={"vec": product_embedding}) # Output: # {'title': 'xyz I35 Kid Headphones with Microphone Volume Limited ...', # 'brand': 'xyz', 'price': '155.06', 'score': '0.293'}, # ... # ... # {'title': 'Kids Headphones with Pouch, xyz Wired ...', # 'brand': 'xyz', 'price': '957.95', 'score': '0.351'}] このパターンを拡張して、埋め込みベースの検索を活用したパーソナライゼーションを実現できます。閲覧したアイテム埋め込みの平均プーリング、アテンションベースのモデル、シーケンスモデルなどの手法を用いて、買い物客のインタラクション履歴からユーザー埋め込みを構築します。単一の商品埋め込みの代わりにユーザー埋め込みをベクトルクエリとして渡すことで、KNN スコアリングがフィルタリングされた集合の中から、買い物客の学習済みの嗜好に基づいて結果をランキングします。 内部の仕組みとパフォーマンス レイテンシーとスループットを、テキストと数値のクエリタイプについて、レプリカなしの単一の cache.r7g.2xlarge ノードを含む 1 シャード構成の ElastiCache for Valkey クラスター上で計測しました。データセットには約 1 GB のデータが含まれており、上記の例で説明したテキスト、タグ、数値、ベクトル属性を持つ 130 万件の製品ドキュメントで構成されています。レイテンシーとスループットの計測には valkey-benchmark を使用しました。 クエリタイプ P50 (ms) 1 クライアント P99 (ms) 1 クライアント QPS 300 クライアント テキスト検索 (完全一致) 0.135 0.255 60,000 前方一致 (タイプアヘッド検索) 0.135 0.279 57,692 数値範囲 (在庫/評価でのフィルタ) 0.175 0.199 24,087 ハイブリッドクエリ – テキスト + 数値範囲 (ファセットブラウジング) 0.135 0.295 52,632 ベクトル検索のレイテンシーとスループットのベンチマークについては、 Announcing vector search for Amazon ElastiCache を参照してください。上記の例では、単一の cache.r7g.2xlarge ノードでのパフォーマンスをテストしています。レプリカ (シャードあたり最大 5 つ) とシャードを追加することで読み取りスループットをスケールし、数百万 QPS に到達できます。各レプリカは独自のインデックスを持ち、独立して検索を処理できますが、レプリカからの読み取りは結果整合性となります。データ容量よりも低レイテンシーを優先する場合は、 single-slot indexes を使用して、インデックス化されたすべてのデータを 1 つのシャードに保持し、ファンアウトのオーバーヘッドを完全に回避してください。シャードを追加することで、クライアントコードを変更せずにメモリ容量を増やすことができます。 ElastiCache はデータの変更をリアルタイムで自動的にインデックス化し、エンジンは各書き込みを確認応答する前にインデックス化します。そのため、それ以降の検索では更新後のデータが返され、read-after-write 整合性が提供されます。この整合性の動作は、マルチキートランザクションや Lua スクリプトでも同様に保たれます。Valkey はマルチスレッドを活用してインデックス処理を複数のスレッドにまたがって実行するため、ElastiCache は書き込みスループットの高いワークロードにおいても検索クエリで高いパフォーマンスを発揮できます。 クリーンアップ このウォークスルーのために ElastiCache クラスターを作成し、不要になった場合は、今後の料金が発生しないように、次の AWS CLI コマンドを使用してクラスターを削除してください。 aws elasticache delete-replication-group --replication-group-id AnyCompany-cache まとめ 本投稿では、ElastiCache における全文検索、完全一致検索、数値範囲検索、およびハイブリッド検索について解説しました。これらの検索タイプのユースケースを取り上げ、検索およびレコメンデーションシステムの構築方法をご紹介しました。全文検索、完全一致検索、数値範囲検索、およびハイブリッド検索は、Valkey 9.0 を実行する ElastiCache のノードベースクラスターにおいて、すべての AWS 商用リージョン、AWS GovCloud (US) リージョン、および中国リージョンで追加費用なしでご利用いただけます。Valkey は、最も制約の少ないオープンソースかつベンダーニュートラルな Redis に代わる選択肢であり、ElastiCache における推奨エンジンです。始めるには、AWS Management Console、AWS SDK、または AWS CLI を使用して、新しい Valkey 9.0 以降のクラスターを作成するか、 既存のクラスターをアップグレード してください。詳細については、 ElastiCache のドキュメント をご覧ください。ご質問やフィードバックは、 AWS re:Post for ElastiCache までお寄せください。 著者について Chaitanya Nuthalapati Chaitanya は AWS インメモリデータベースサービスのシニアテクニカルプロダクトマネージャーで、Amazon ElastiCache for Valkey を担当しています。以前は、生成 AI、機械学習、グラフネットワークを活用したソリューションを構築していました。仕事以外の時間では、Chaitanya は趣味を集めることに忙しく、現在はテニス、スケートボード、パドルボードを楽しんでいます。 Karthik Subbarao Karthik は Amazon ElastiCache のシニアソフトウェアエンジニアで、オープンソースの Valkey プロジェクトに積極的に貢献しています。分散システム、データベース、Rust、そして全般的にソフトウェア開発・技術を通じたイノベーションに情熱を持っています。 Ian Childress Ian は AWS のソフトウェア開発マネージャーで、全文検索インフラストラクチャを含む Valkey モジュールおよび統合機能を構築するチームを率いています。仕事以外では、Ian はホッケーをプレイし、Go 言語で高性能システムを書くことに没頭する飽くなき探求者です。夏になると、氷から湖へと舞台を移し、毎週末家族とウェイクサーフィンを楽しんでいます。 Eran Balan Eran は AWS のスペシャリストソリューションアーキテクトで、インメモリデータベースおよび Amazon ElastiCache を専門としています。EMEA 地域のお客様と協力し、キャッシングアーキテクチャの設計、パフォーマンスの最適化、Redis OSS から Valkey への移行など、さまざまな移行を支援しています。仕事以外では、Eran はミュージカルや演劇の観劇、ハイキング、オープンウォータースイミングを楽しんでいます。 プロジェクト全体を通じて、ビジョン、指導、そして実践的な貢献をいただいた Allen Samuels 氏に心より感謝申し上げます。 本記事は、 Full-text, exact-match, range, and hybrid search on Amazon ElastiCache を翻訳したものです。翻訳は Solutions Architect の Hayato Tsutsumi が担当しました。
Google Workspace MCPサーバーの概要 そもそもMCPとは何? MCPとは、Model Context Protocol(モデル・コンテキスト・プロトコル)の略です。 端的に説明すると、AIエージェントやAIアプリが、外部のデータやツールに安全・標準的につながるための共通規格です。公式ドキュメントでは、AIアプリと外部システムを接続するためのオープンソース標準と説明されています。


























