Elasticsearch 入門。その2
Elasticsearch 初学者の中村です。 入門その2では、 Bulk APIや検索方法について学んだことを書いていきます。
その1はこちら
Bulk API
前回の記事でCRUD処理を行うAPIを紹介しましたが、大量のドキュメントを処理するのに1件ずつAPIを実行していては時間やリソースの無駄使いな為、Elasticsearchでは一括処理用のAPIが用意されています。
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html
使い方
Bulk APIでは、複数のドキュメントの登録、削除、更新等が1回のAPI呼び出しで実行可能です。
POST /<index>/_bulk
にJSONL(NDJSON)フォーマットで操作したいドキュメント情報を指定します。
POST /shop/_bulk {"index" : {"_id" : "shop_001"}} {"name" : "shop name", "address" : "Saitama-ken xxx"} {"delete" : {"_id" : "shop_002"}} {"update" : {"_id" : "shop_001"}} {"doc" :{ "address" : "Tokyo-to xxxxx"}}
一括でリクエストすることにより1件ずつ処理するよりもオーバーヘッドが少なくなり、負荷軽減や速度向上が見込めます。
ファイル指定
JSONLファイルを指定指定することも可能です。
https://www.elastic.co/guide/en/kibana/7.6/tutorial-build-dashboard.html#load-dataset
公式で配布されているサンプルデータのaccounts.jsonをダウンロードし、以下のcurlコマンドを実行します。
% curl -H 'Content-Type: application/x-ndjson' -XPOST 'localhost:9200/account/_bulk?pretty' --data-binary @accounts.json "took" : 327, "errors" : false, "items" : [ { "index" : { "_index" : "bank", "_type" : "account", "_id" : "1", "_version" : 1, "result" : "created", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 0, "_primary_term" : 1, "status" : 201 } }, ...
検索
上記で登録したaccounts.jsonのドキュメントを少し修正して色々な検索を試します。 Elasticsearchではインデキシングしたフィールドに対してSearch APIで検索を行うことが出来ます。
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html
GET /<index>/_search POST /<index>/_search
Search APIではGETとクエリパラメータやリクエストボディの組み合わせで検索も可能ですが、本稿ではPOSTとリクエストボディで検索条件を指定する形で記載いたします。
Match query
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html
match queryは検索条件に指定したフィールドの値が等値のドキュメントを返してくれます。
POST account/_search { "query": { "match": { "firstname": "Amber" } } }
上記検索条件は firstname
フィールドに Amber
という値を持つドキュメントを検索して、Elasticsearchは該当のドキュメントを返してくれました。
Elasticsearchでは検索結果をレスポンスJSON .hits.hits[]
配下に含めて返してくれます。ヒットした件数は .hits.total.value
になります。
AND検索
match queryはデフォルトでは OR検索を行います。 試しに先程の検索条件を変更して検索します。
"address": "880 Holmes Lane"
検索結果をみると、 address
に 880
,Holmes
,Lane
の何れかが含まれたドキュメントが返されます。
AND検索を行いたい場合は operator
に and
を指定します。
POST account/_search { "query": { "match": { "address": { "query": "880 Holmes Lane", "operator": "and" } } } }
operator
を指定することにより、 880
,Holmes
,Lane
が全て含まれたドキュメントのみ検索が出来ます。
minimum_should_match
実際に検索サービスを運用する際、ORでは緩くANDでは厳しすぎるケースがあります。
その場合には minimum_should_match
を指定することで、最低限含まれる単語を指定することが出来ます。
POST account/_search { "query": { "match": { "address": { "query": "880 Holmes Lane", "minimum_should_match": 2 } } } }
この場合は、 880
,Holmes
,Lane
の何れか2つが一致するドキュメントが返ります。
Match phrase query
match queryでは条件に一致するドキュメントを検索できましたが、語順は考慮されていませんでした。
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html
POST account/_search { "query": { "match": { "address": { "query": "Kings Place", "operator": "and" } } } }
上記match queryではKings
Place
が語順関係なくヒットしています。2件目では Kings Holmes Place
と間に別の語句が入っていてもヒットします。
match_phrase
を使うことで語順も加味した検索を行うことが出来ます。
POST account/_search { "query": { "match_phrase": { "address" : "Kings Place" } } }
match_phrase
では、検索条件の単語が全て含まれていて、それぞれの単語が近くに存在すること。を検索するためコストが非常に高いので注意です。
slopパラメータ
match_phrase
では厳しすぎるケースに対応するため緩めるために slop
パラメータを指定可能です。
POST account/_search { "query": { "match_phrase": { "address" : { "query": "Kings Place", "slop": 1 } } } }
"slop" : 1
を指定することにより、検索条件の単語間がどれくらい離れていいかを指定ができます。
上記を実行すると先程の条件ではヒットしなかったドキュメントがヒットするようになります。
Range query
range queryでは値を範囲指定して検索することが出来ます。
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-range-query.html
以下では、 age
フィールドに20以上30以下の値を持つドキュメントを検索しています。
POST account/_search { "query": { "range": { "age": { "gte": 20, "lte": 30 } } } }
rangeは日付型のフィールドにも有効で、Elasticsearchでは検索用に様々な表現が用意されています。 下記クエリ例ではタイムスタンプフィールドが昨日から今日までの値を持つドキュメントを検索します。
POST <index>/_search { "query": { "range": { "timestamp": { "gte": "now-1d/d", "lt": "now/d" } } } }
Bool query
実際のシステムの検索条件は複雑で、複数の条件で検索することが多くなると思います。その際にはbool queryを利用します。
bool queryは以下の組み合わせで条件を構成していきます。
- must
- 条件が一致しなければならない。スコアに影響する
- must_not
- 条件が一致してはいけない。スコアに影響しない
- should
- 条件が一致してもしなくてもよい。スコアに影響する
- filter
- 条件が一致しなければならない。スコアに影響しない
mustとfilterが同じに見えますが、スコア算出の箇所が違います。ここに関しては後述いたします。
must
下記では、 gender
が M
に一致するドキュメントを検索します。
POST account/_search { "query": { "bool": { "must": [ { "match": { "gender": "M" } } ] } } }
今まで紹介した検索条件とは構文が少し代わり .query.bool.must[]
の下に詳細な条件を指定していきます。
must_not
先程の条件に state
が IL
に一致しないという条件を追加します。
これにより、 gender
はM
かつ state
はIL
ではない。という条件になります。
POST account/_search { "query": { "bool": { "must": [ { "match": { "gender": "M" } } ], "must_not": [ { "match": { "state": "IL" } } ] } } }
should
should
はヒット件数には影響しないけども、条件にヒットしたドキュメントのほうがスコアが高くなります。
先程の条件に30歳未満のshouldを追加してみます。
POST account/_search { "query": { "bool": { "must": [ { "match": { "gender": "M" } } ], "must_not": [ { "match": { "state": "IL" } } ], "should": [ { "range": { "age": { "lt": 30 } } } ] } } }
先ほどとヒットした件数(.hits.total.value
)の値は変わりませんが、ヒットしたドキュメントの順番が変わり、20台のドキュメントが先に返ってきていることが分かります。
filter
filter
は条件に一致していないといけませんが、スコアには影響しません。
先程の条件にfilterにcity
がCoalmont
の場合。という条件を追加します。
POST account/_search { "query": { "bool": { "must": [ { "match": { "gender": "M" } } ], "must_not": [ { "match": { "state": "IL" } } ], "should": [ { "range": { "age": { "lt": 30 } } } ], "filter": [ { "match" : { "city" : "Coalmont" } } ] } } }
ヒットした件数は1件に減り、ヒットしたドキュメントのスコア(.hits.hits._score
)は先程と変わっていません。
filter
は単純にドキュメントを絞りたい場合に利用します。
スコア
検索結果レスポンスのスコア(.hits.hits._score
) は検索条件のクエリに、該当のドキュメントがどれくらい類似しているかの値になります。
スコアはヒットしたドキュメントそれぞれに対して計算されます。
ちなみに .hits.max_score
にはヒットしたドキュメントの中で最大のスコアが入ります。
Elasticsearchでは類似度の計算にBM25というアルゴリズムをデフォルトで利用します。
BM25は以下の計算を行います。
- TF(term frequency)
- 検索単語の頻出頻度が多い程スコアが高くなる
- IDF(inverse document frequency)
- 検索単語がたくさんのドキュメントに存在するほど、スコアが低くなる
- Field length
- 該当フィールドの値の長さが平均より短いほうがスコアが高くなる
上2つはTF-IDFと呼ばれる手法です。
TFでは検索した単語がよく頻出するドキュメントはスコアが高くなります。
Elasticsearch
で検索した場合、1回出現するドキュメントと30回出現するドキュメントでは、後者のほうが関連性が高いと判断されます。
IDFでは単語の希少度を判断しスコア付けします。
AWS
Elasticsearch
_score
で検索した場合、AWS
や Elasticsearch
を含んだドキュメントは沢山あるのでスコアは低めですが、 _score
は他の単語より出現頻度が少ない単語なので、含まれているドキュメントは関連性が高いと判断されます。
Field lengthはフィールドの値の長さが平均より低い場合にスコアが高くなります。
Elasticsearch
で検索し、1ページと100ページのドキュメントがヒットし、それぞれ単語が1回しか出てこなかった場合、1ページのドキュメントの方が関連度が高いという判断になります。100ページに1度しか出現しないのであれば、 Elasticsearch
に言及しているページでは無さそうですね。
アルゴリズムの詳細については、 Elastic社のブログに詳しく書いてあります。
最後に
今回は検索の方法について学びました。 RDBMSにも全文検索を実現するための機能は提供されていますが、利用するにはコストや負荷を気にしてしまいがちです。
Elasticsearchでは専用APIを利用することにより、簡単に低コストで検索することが出来るのが便利ですね。
次回はマッピングやアナライザーについて書いていこうと思います。