トリバゴのように一つのホテルの価格を複数のサービスで比較をすることを考えるとき
検索結果はホテルをあるソート順(おすすめ順)で並べた上で、さらに各ホテルの中で比較したいサービスがあるソート順(価格低い順)に並んでいるようにしたい
これを実現するために、ElasticsearchのField Collapsing という機能があることを知ったので調べてみた
Field Collapsing
Field Collapsing は指定されたフィールドで検索結果をグルーピングする機能
Field Collapsingを使うためには、クエリに collapse 句を追加し、collapse 句の中でグルーピングのキーとなるフィールドを指定する
キーに指定するフィールドは、keyword
か numeric
である必要がある
次のクエリは「elasticsearch」という文言を含むつぶやきを ユーザー毎にグルーピングし、いいねの数で並べ替えている
GET /twitter/_search { "query": { "match": { "message": "elasticsearch" } }, "collapse" : { "field" : "user" }, "sort": ["likes"], "from": 10 }
このときの、検索結果総数(total)は「elasticsearch」とつぶやいたツイート数であり、重複しないユーザの数などはわからないので注意が必要
Field Collapsingの展開
inner_hits
オプションをつけると検索結果の中の各グループに検索結果に含めることができる
次のクエリは「elasticsearch」という文言を含むつぶやきを ユーザー毎にグルーピングし、
さらに、グループごとに日付の昇順にソートした上位5件をlast_tweets
という名前で取得している
max_concurrent_group_searches
で内部クエリを何並列に実行するかを指定する
この値はデフォルトではノード数とスレッドプールサイズによって自動的に決定される
GET /twitter/_search { "query": { "match": { "message": "elasticsearch" } }, "collapse" : { "field" : "user", "inner_hits": { "name": "last_tweets", "size": 5, "sort": [{ "date": "asc" }] }, "max_concurrent_group_searches": 4 }, "sort": ["likes"] }
複数のinner_hits
を取得することも可能
GET /twitter/_search { "query": { "match": { "message": "elasticsearch" } }, "collapse" : { "field" : "user", "inner_hits": [ { "name": "most_liked", "size": 3, "sort": ["likes"] }, { "name": "most_recent", "size": 3, "sort": [{ "date": "asc" }] } ] }, "sort": ["likes"] }
各グループごとにリクエストを送信することになるため、inner_hits
が多すぎる場合は処理が大幅に遅くなる可能性があるので注意
第2レベルのField Collapsing
第2レベルのField Collapsingもサポートされている
次のクエリは「elasticsearch」という文言を含むつぶやきを 国ごとにグルーピングし、 さらに、各国のユーザごとのツイートをグルーピングしたものを取得している
GET /twitter/_search { "query": { "match": { "message": "elasticsearch" } }, "collapse" : { "field" : "country", "inner_hits" : { "name": "by_location", "collapse" : {"field" : "user"}, "size": 3 } } }
検索結果
{ ... "hits": [ { "_index": "twitter", "_type": "_doc", "_id": "9", "_score": ..., "_source": {...}, "fields": {"country": ["UK"]}, "inner_hits":{ "by_location": { "hits": { ..., "hits": [ { ... "fields": {"user" : ["user124"]} }, { ... "fields": {"user" : ["user589"]} }, { ... "fields": {"user" : ["user001"]} } ] } } } }, { "_index": "twitter", "_type": "_doc", "_id": "1", "_score": .., "_source": {...}, "fields": {"country": ["Canada"]}, "inner_hits":{ "by_location": { "hits": { ..., "hits": [ { ... "fields": {"user" : ["user444"]} }, { ... "fields": {"user" : ["user1111"]} }, { ... "fields": {"user" : ["user999"]} } ] } } } }, .... ] }