Solr に Reciprocal Rank Fusion (RRF) がやってくる! | ブログ | KandaSearch

BLOG

Solr に Reciprocal Rank Fusion (RRF) がやってくる!

著者: Elpidio Gonzalez Valbuena

  

投稿日: 2025年11月27日

    
  • Solr

ハイブリッド検索の簡単なおさらい

ハイブリッド検索とは、この文章では「従来のキーワード検索」と「ベクトルベースのセマンティック検索」を、1 つの検索・ランキングパイプラインの中で組み合わせることを指します。Solr はすでに両方をサポートしています。すなわち、BM25 などの古典的なスコアリングモデルによるレキシカル(キーワード)マッチと、solr.DenseVectorField を使った knn クエリによるベクトル類似度検索です。ハイブリッド検索は、前者の「精度」とユーザが慣れ親しんだキーワード挙動、後者の「ロバストさ」と埋め込みモデルによる意味的な一般化の両方を活かすことを狙っています。

これを本番環境でうまく回すためのパターンやトレードオフは多岐にわたります。詳しい導入部分の繰り返しは避け、より深く知りたい方には以前のポスト「検索はハイブリッドにすべき理由:キーワード検索とセマンティック検索の融合」を参照していただくのがよいでしょう。そちらでは、Solr におけるハイブリッド検索の基本的なアーキテクチャやモチベーション、よくある落とし穴を紹介しました。今回の記事はその続編として、Solr におけるハイブリッド検索周りで「何が変わってきたのか」に焦点を当てます。

Solr の現状の機能と制約をあらためて振り返る

Alessandro Benedetti は自身の記事「Hybrid Search with Apache Solr」の中で、「Solr はすでに実務レベルでハイブリッド検索をサポートしている」と強調しています。彼のレシピはシンプルで、レキシカルクエリと knn ベクトルクエリから 2 つの候補集合を取得し、それらを Solr 内部の既存機能で組み合わせるというものです。具体的には、候補集合の和集合・積集合を扱う Boolean Query Parser、スコアの正規化や加算・乗算に使える function query、そしてレキシカル特徴とベクトル特徴をデータ駆動で組み合わせたい場合には Learning To Rank (LTR) モデルを利用します。これにより、Solr におけるハイブリッド検索は「いつか実装されるかもしれない機能」ではなく、すでに現実のシステムで利用可能なアプローチであることが分かります。

一方で、このアプローチには依然として課題もあります。スコアの正規化を手作業で設計する必要があり、追加の function query を組み立てたり、場合によっては LTR モデルを別途学習させることになります。これらは強力なツールですが、「レキシカルスコアとベクトルスコアを簡潔に組み合わせたい」チームにとっては、毎回カスタムの融合戦略を考えなければならないというハードルにもなります。

以前のブログ記事でも、Solr と Lucene の「高度なハイブリッド検索」に関する out-of-the-box サポートには以下のような制約があると述べました。

  • レキシカルランキングとベクトルランキングを賢くマージするネイティブなスコア融合メカニズムがない。

  • BM25 スコアとベクトル類似度スコアはまったく異なるスケール上にあり、単純な足し合わせでは正しく働きにくい。

  • 第一級の融合コンポーネントがないため、「なぜこの順でドキュメントが並んでいるのか」を説明しづらい。

Reciprocal Rank Fusion は、このギャップを埋めるためにコミュニティが選ぼうとしている解決策と言えます。よく理解されたシンプルな融合アルゴリズムを Solr がネイティブ機能として提供できれば、ハイブリッド検索のたびにカスタムなスコア設計を行う必要はなくなるはずです。

ハイブリッド検索における Reciprocal Rank Fusion

Reciprocal Rank Fusion(RRF)は、複数のランキング済み結果リストを 1 つの一貫したランキングに統合するシンプルかつ強力な手法です。Solr の文脈では、これらのリストは検索スタック内の異なる「エキスパート」から出てくるものだと考えると分かりやすいでしょう。たとえば、レキシカルなエキスパート(BM25 などのキーワードクエリ)と、1 つ以上のベクトルエキスパート(k 最近傍クエリ)といった具合です。

RRF のポイントは、「スコアそのもの」ではなく「各リスト内での順位」に注目することです。相容れないスケールで計算された生スコア同士を直接比較する代わりに、各結果リストにおける順位だけを見ます。複数のランキングで上位に現れるドキュメントは高く評価され、どこか 1 回だけ、あるいは下位にしか現れないドキュメントは自然と重みが下がります。こうした性質から、RRF は複数システムのランキングを統合する標準的な手法として広く使われており、TREC のような大規模評価でも有効性が示されています 1

形式的には、各ドキュメントは「そのドキュメントが現れる各リスト」から 1 / (k + rank) という貢献値を受け取ります。ここで rank はそのリスト内の順位、k は下位の順位の影響をどれくらい早く減衰させるかを決める小さな定数です(通常は k = 60 とされることが多いです)。すべてのリストからの貢献値を足し合わせることで、異なる検索戦略間の「合意」の度合いを捉えた RRF スコアが得られます。

このアプローチが Solr にとって魅力的なのは、BM25 とベクトル類似度の間でスコアの正規化を行う必要がないからです。必要なのは「各クエリにおける順位」だけであり、それは Solr がすでに提供している情報です。

これを Solr に実装することは、先ほど挙げたハイブリッド検索の課題を乗り越えるうえでの第一歩になります。ハイブリッド検索を利用するユーザにとっては、「スコア分布が変わった途端にランキングが大きく崩れる」といった驚きを減らし、なぜ特定のドキュメントが上位に来たのかをより説明しやすくしてくれるはずです。

これまでの試みと現在の状況

以前のポストでは、「しかし現在、その開発は停滞しており、再開にはコミュニティや商用ベンダーからの支援が必要とされています。」(その貢献の開発は現在停止しており、再開にはコミュニティもしくは商業的なサポートが必要である)と書きました。この一文が指していたのは、Alessandro Benedetti による最初の RRF 実装の試みです。これは SOLR-17319 のもとで提案されたもので、PR #2489 として実装されました。Solr の既存の JSON Request API の中で RRF が動くことを示した、最初の具体的な一歩でした。

ざっくり言うと、このパッチは「1 つの JSON リクエストの中で複数のサブクエリを送信し、その結果リストを Solr が 1 つのランキングにまとめる」という形で RRF を Solr に組み込むものでした。機能面では魅力的でしたが、実装はコアのクエリ処理コードに直接入り込んでおり、単一シャードでの利用を明示的にターゲットにしていました(分散検索のサポートは最低限に留まっていました)。この点について、Solr メンテナからは「長期的なメンテナンス性」や「実験的な機能をどこに置くべきか」といった観点から懸念が示されました。

Alessandro 自身も、レビュアが提案したよりクリーンな設計へと進化させるには相当な追加作業が必要であり、自身の時間やリソースではそこまで手を入れられないことを認めています。その後のカンファレンス資料では、この取り組みを「work in progress - paused, waiting for funding/time(進行中だが一時停止中。資金や時間待ち)」とラベル付けしています 2

その後、追加のアップデートが無いまま時間が経過し、この PR は 2025 年 3 月に GitHub のボットによって自動的にクローズされました。

言い換えると、最初の RRF 実装は議論の方向性を形作り、さまざまなエッジケースを可視化するという重要な役割を果たしましたが、機能としてリリースするために必要な「アーキテクチャ面での合意」と「継続的なメンテナのコミットメント」には届かなかった、ということになります。


次の本格的なネイティブ RRF 実装の試みは、Sonu Sharma による「combined query」機能の PR #3418 です。この作業のゴールは、「1 つの JSON リクエストの中で複数種類のサブクエリ(例: BM25 キーワードクエリと 1 つ以上の knn ベクトルクエリ)を記述し、それらをすべてのシャードに対して実行したうえで、結果リストを設定可能なアルゴリズム(現時点では RRF のみが実装済み)でマージできるようにする」ことです。

このゴールを達成するために、パッチでは既存の QueryComponent にロジックを継ぎ足すのではなく、専用の CombinedQueryComponent と、それに対応する CombinedQueryResponseBuilder を導入しています。これらの新しいコンポーネントは、サブクエリを各シャードにファンアウトし、その結果を集約し、最後に融合ステップを適用する役割を担います。また JSON Query DSL には、この振る舞いを明示的に起動するための "combined query" エントリポイントが追加されます。

分散処理の扱いについては、実装の過程でいくつかの試行錯誤がありました。最初の設計では、2 つの戦略が試されています。1 つは「各シャードが返してくる結果に対してそれぞれ RRF を適用し、その後に統合する」方法、もう 1 つは「すべてのシャードから結果を集めてから、コーディネーティングノードで一度だけ RRF を適用する」方法です。

当初は設定パラメータでどちらの戦略を選ぶか切り替えられるようになっていましたが、レビューの中で David Smiley は前者のアプローチに対し、「シャード数が増えるほど shard interleaving(シャード間の結果が不自然に入り混じる現象)が起きやすくなり、全体として一番良いドキュメントがあるにもかかわらず、それが結果に反映されにくくなる」と指摘しました。これを受けて Sonu は実装をシンプルにする方向で修正し、「各シャードごとに RRF をかけるモード」を削除し、「コーディネータ側で 1 回だけ RRF を適用する」方式に絞り込みました。これにより、最終的な RRF ランキングは「すべてのシャードを見渡したグローバルな視点」で決定されるようになります。

Sonu の PR には RRF ロジックに関するユニットテストや、SolrCloud を対象とした分散テストが含まれており、通常の ./gradlew check ワークフローにも統合されています。また、Solr コミッターである Christine PoerschkeDavid Smiley らによる詳細なレビューもすでに行われており、そのフィードバックを受けてパッチは継続的にブラッシュアップされています。

執筆時点ではこの PR はまだオープンの状態ですが、最初の試みに比べてはるかにモジュラーで分散処理を強く意識した設計になっており、「Solr に RRF をどのように統合すべきか」というコミュニティの現在の議論の中心となっています。SOLR-17319 自体もステータスが “Patch Available” となっており、Solr Dense Vector フォーカスグループのロードマップにも明示的に挙げられています。このことからも、この取り組みはすでに単なる実験段階を超えており、「最後のレビューと仕上げを待っている機能」にかなり近いところまで来ていると言えるでしょう。

KandaSearch では、この取り組みは Solr ユーザーにとってハイブリッドサーチをより扱いやすいものにするうえで戦略的に重要だと考えており、単に外から声援を送るだけでなく、具体的なかたちで貢献したいと考えています。開発チームではすでにこのパッチを適用し、Combined Query と RRF を現実的なワークロードに対して動かしながら、その知見をコミュニティに還元する準備を進めています。次回の記事では、そのプロセスを、ブランチをローカルでビルドして実行する方法から、実際に投げたハイブリッドクエリの種類、初期実験から得られた示唆に至るまで、段階を追って紹介します。最終的な目標は、この有望な設計を Solr ユーザーが評価できる具体的な成果として提示し、将来の Solr リリースへの取り込みに一歩でも近づけることです。

お見積もり・詳細は KandaSearch チームに
お気軽にお問い合わせください。

お問い合わせ