Elasticsearchのmax clause countについて

普通につかっていると気にせず済むことは多いですが、たまに検索時のエラーの原因になるmax clause countについてまとめます。

max clause countの何が問題なのか

max clause countを超えるクエリが生成された場合、検索時にtoo_many_clausesエラーが発生します。

そもそもmax clause countってなに?

LuceneのBooleanQueryに含めることのできる句(clause)の最大数。

句(clause)にはmust, should, filter, must_notの4つの種類があります。 github.com

Luceneの実装では、BooleanQueryクラスにBooleanClauseクラスを追加していくようになっています。

lucene.apache.org

max clause countの制限は、検索が大きくなりすぎてCPUやメモリを多く消費することを防ぐために設けられています。

Luceneの実装でdefaultは1024が設定されています。

lucene.apache.org

Boolean Queryに条件をいれすぎなければいいだけでは?

半分正解。

ElasticsearchのBoolean QueryはLuceneのBooleanQueryに展開されます。

したがって、Boolean Queryに大量の条件を追加しないというのがひとつの対策です。

Boolean query | Elasticsearch Guide [8.6] | Elastic

ですが、 Elasticsearch では Boolean Queryだけでなく他のクエリでも内部的に Lucene の BooleanQuery を組み立てます。

事例を見てみましょう。

以下の設定でtest_indexを作成します。

{
  "settings": {
    "analysis": {
      "tokenizer": {
        "bigram_tokenizer": {
          "type": "ngram",
          "min_gram": 2,
          "max_gram": 2
        }
      },
      "analyzer": {
        "bigram_analyzer": {
          "tokenizer": "bigram_tokenizer"
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "bigram_analyzer"
      }
    }
  }
}

次に2つドキュメントを追加します

POST test_index/_doc/1
{
    "title": "宇宙兄弟"
}

POST test_index/_doc/2
{
    "title": "ワールドトリガー"
}

単純にmatchクエリで検索してみるとこの2つのドキュメントがヒットします。

{
  "query": {
    "match": {
      "title": "宇宙ワールド"
    }
  }
}

が、この際内部でどういうクエリが生成されるかsearch_profilerで見てみましょう。

bigram時のsearch profiler

今回使ったElasticsearchのMatch Queryはテキスト解析した結果からクエリを組み立てます。 今回はbigramを使っているため、2文字ずつに分割されたtokenの数だけtermQueryを作成してBooleanQueryでまとめています。

ngramの設定でmin_gramとmax_gramの数が違う、kuromoji tokenizerでnbest_costを変える、検索する文字列が長いなど、クエリ展開時に生成されるtokenが多くなるような設定にしている場合、うっかり超えてしまうことはあるので注意が必要です。