トリバゴのように一つのホテルの価格を複数のサービスで比較をすることを考えるとき
検索結果はホテルをあるソート順(おすすめ順)で並べた上で、さらに各ホテルの中で比較したいサービスがあるソート順(価格低い順)に並んでいるようにしたい
これを実現するために、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"]}
}
]
}
}
}
},
....
]
}