1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Lucene (Elasticsearch, Solr) のQuery#rewriteの役割と実装と、SolrのRankQueryにおける対応

Posted at

検索ライブラリApache Luceneにおいて、ユーザからのクエリはメモリ上ではQueryクラスのオブジェクトで表現されます(Luceneに依存する検索エンジンElasticsearchやSolrにおいても同様)。そして、Queryクラスにはrewriteメソッドが実装されています。

Query#rewriteの役割

このメソッドの役割は、主に以下の2つあるようです。

  • そもそも直接は実行できないクエリを実行可能にする。
  • クエリをパフォーマンス面で最適化する。

直接は実行できないクエリを実行可能にする例

直接は実行できないクエリの例として、プレフィックスクエリがあります。以下、具体的な例はSolr 8.6.3と、サンプルとして同梱されているtechproductsコレクションに基づきます。以下のリクエストは:

/solr/techproducts/select?q=cat:hard*

メモリ上では大まかに以下のように表現され、rewriteされます。

PrefixQuery "cat:hard*"

↓ rewrite

MultiTermQueryConstantScoreWrapper "cat:hard*"
  ┗ PrefixQuery "cat:hard*"

↓ rewrite

ConstantScoreQuery "ConstantScore(cat:hard cat:hardcover)"
  ┗ BooleanQuery "cat:hard cat:hardcover"
    ┣ TermQuery "cat:hard"
    ┗ TermQuery "cat:hardcover"

つまり、プレフィックスをマッチするタームに展開するのは、PrefixQuery#rewriteの役割のようです。

クエリをパフォーマンス面で最適化する例

クエリをパフォーマンス面で最適化する例として、例えば以下のように明らかに最適化できそうなリクエストを投げた場合:

/solr/techproducts/select?q=cat:book AND cat:book

メモリ上では大まかに以下のように表現され、rewriteされます。

BooleanQuery "+cat:book +cat:book"
  ┣ TermQuery "cat:book"
  ┗ TermQuery "cat:book"

↓ rewrite

BooleanQuery "+(cat:book)^2.0"
  ┗ BoostQuery "(cat:book)^2.0"
    ┗ TermQuery "cat:book"

↓ rewrite

BoostQuery "(cat:book)^2.0"
  ┗ TermQuery "cat:book"

Query#rewriteの実装

例の通り、1つのリクエストに対してQuery#rewriteは何度か実行されることがあります。これは、クエリが変更される限り、繰り返し実行されるようになっているからです。コードで言うと以下の箇所 (IndexSearcher#rewrite. Query#rewriteとは異なります) です。

  public Query rewrite(Query original) throws IOException {
    Query query = original;
    for (Query rewrittenQuery = query.rewrite(reader); rewrittenQuery != query;
         rewrittenQuery = query.rewrite(reader)) {
      query = rewrittenQuery;
    }
    return query;
  }

引用元:https://github.com/apache/solr/blob/e001c2221812a0ba9e9378855040ce72f93eced4/lucene/core/src/java/org/apache/lucene/search/IndexSearcher.java#L674-L681

このコードから、クエリが変更されたかどうかの判定は、インスタンスの同一性の判定 (!=) で行われていることが分かります。Query#rewriteで具体的に何をするかはQueryの目的によりますが、クエリを変更した場合は、必ず新しいインスタンスを作成して返す必要があるということになります。

SolrのRankQueryにおける対応

Query#rewriteを意識する具体的な局面としては、Solrに独自のRankQueryを実装するときが挙げられます。RankQueryを実装してドキュメントを並べ替えること自体については、以下の記事などを参照してください。

RankQuery#rewriteで具体的に何をするかも、RankQueryの目的によります。しかし、RankQueryは元のクエリを内包していますので、少なくとも元のクエリのrewriteメソッドも呼ぶ必要があり、かつ、内包するクエリがrewriteによって変更された場合には、RankQueryとしても新しいインスタンスを返す必要があります。

例えばSolrに同梱のRankQueryであるReRankQueryでは、以下の箇所 (AbstractReRankQuery#rewrite) でQuery#rewriteを実装しています。

  public Query rewrite(IndexReader reader) throws IOException {
    Query q = mainQuery.rewrite(reader);
    if (q != mainQuery) {
      return rewrite(q);
    }
    return super.rewrite(reader);
  }

引用元:https://github.com/apache/solr/blob/e001c2221812a0ba9e9378855040ce72f93eced4/solr/core/src/java/org/apache/solr/search/AbstractReRankQuery.java#L72-L78

これは、上述した少なくとも必要な処理だけを行う例になっています。ここでは3種類のQuery#rewriteが呼ばれていて、ややこしいのですが、以下のような使い分けです。

  • mainQuery.rewriteで、元のクエリのrewriteメソッドを呼んでいます。
  • rewrite(q)は、AbstarctReRankQuery#rewrite(Query)を呼んでいます。ややこしいのですが、これは本記事の主題であるQuery#rewrite(IndexSearcher)とは異なり、単にAbstarctReRankQueryの新しいインスタンスを返すメソッドです。
  • super.rewriteは、デフォルトのQuery#rewriteを呼んでいます。これは今のところ、単にreturn thisします。
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?