Elasticsearchをバージョンアップしたら検索結果が変わった話

Elasticsearchをバージョンアップしたら検索結果が変わった話

Clock Icon2022.03.16

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

prismatix 事業部の中村です。

先日、Elasticsearch 8.0 がリリースされました。

Elasticsearch ではメジャーバージョンアップした際に 破壊的な変更が多数含まれおり、 そのままバージョンアップすると意図しない検索結果になってしまう可能性がある為、注意が必要です。

今回は Elasticsearch を6から7にバージョンアップした際に、破壊的な仕様変更が原因で検索結果が変わってしまった件をまとめます。

再現クエリ

PUT apple/_doc/1
{
  "item_name" : "iPhone SE",
  "label" : "SE3"
}

POST apple/_search
{
  "query": {
    "bool": {
      "filter": {
        "bool": {
          "must": {
            "match": {
              "item_name": "iPhone SE"
            }
          },
          "should" : {
            "terms" : {
              "label" : ["SE2", "SE"]
            }
          }
        }
      }
    }
  }
}

1つめのリクエストでは、apple Index を作成して iPhone SE3のドキュメントを登録します。

2つめのリクエストは AND条件にitem_name=iPhone SE、 OR条件に label=SE/SE2 を指定した検索クエリです。

これを Elasticsearch 6と7それぞれにリクエストして、検索結果を見てみます。

Elasticsearch 6までの検索結果

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 0,
    "max_score" : null,
    "hits" : [ ]
  }
}

label が SE/SE2 のドキュメントが存在しない為、 検索結果は0件になります。

Elasticsearch 7.0 からの検索結果

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.0,
    "hits" : [
      {
        "_index" : "apple",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.0,
        "_source" : {
          "item_name" : "iPhone SE",
          "label" : "SE3"
        }
      }
    ]
  }
}

さっきとは変わり検索結果にiPhone SE3のドキュメントが返ってきました。 何故でしょうか?

原因

調べた所、 Elasticsearch 7.0 からの仕様変更 が原因でした。

Elasticsearch には minimum_should_match という、should 条件が最低限一致しなくてはならない数。というリクエスト項目があります。 そのデフォルト値の仕様が6.8 までと7.0 からでは変わりました。

7系の公式ドキュメントから、 minimum_should_match の説明を引用します。

If the bool query includes at least one should clause and no must or filter clauses, the default value is 1. Otherwise, the default value is 0.

bool クエリが少なくとも 1 つの should 節を含み、must 節または filter 節がない場合、デフォルト値は 1 になります。その他では 0 になります。

PR を確認した所、filter コンテキスト内の bool に must/filter と should が指定された場合にクエリビルダーで minimum_should_match に 1 を指定していた処理を削除しているのが分かります。

これまでは minimum_should_match の値を Elasticsearch 側で調整していましたが、この対応により Lucene 側で判断させるようにしたらしいです。

この対応により、先程実施した検索クエリは Elasticsearch 6.8までと7.0からの解釈が異なるようになりました。

Elasticsearch 6.8 までの解釈

minimum_should_match が 1 に設定されるため、下記に一致するドキュメントがヒットします。

  • item_name : "iPhone SE"
  • label : "SE2"

Elasticsearch 7.0 からの解釈

minimum_should_match は 0 になるため、shouldの条件は無視され item_name のみ一致するドキュメントがヒットします。

  • item_name : "iPhone SE"

対応方法

Elasticsearch 7系にバージョンアップしてから以前と同様の検索結果を得るには、下記のように minimum_should_match に 1 を指定する必要があります。

POST apple/_search
{
  "query": {
    "bool": {
      "minimum_should_match": 1, 
      "filter": {
        "bool": {
          "must": {
            "match": {
              "item_name": "iPhone SE"
            }
          },
          "should" : {
            "terms" : {
              "label" : ["SE2", "SE"]
            }
          }
        }
      }
    }
  }
}

これにより、 should の条件も加味されるようになり以前と同様の結果結果を得ることが出来ます。

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 0,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  }
}

最後に

今回は Elasticsearch のバージョンアップで検索結果が変わってしまった事例を書きました。

8.0のリリースでも 破壊的な変更が多数あるため、バージョンアップ前に利用しているクエリへの影響は確認するようにしましょう。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.