ES學習記錄9.4——請求體搜尋(欄位摺疊Filed Collapsing和搜尋後Search After)
1. 欄位摺疊
ES允許基於欄位值摺疊搜尋結果,ES僅對排序後文檔的頂部文件執行成摺疊操作,比如從每個推特使用者獲取它們最好的推文並通過其他使用者的點贊數進行排序(升序):
// 建立索引,這裡一定要將user欄位的型別設定為keyword或numeric
curl -X PUT "localhost:9200/twitter" -H 'Content-Type: application/json' -d'
{
"mappings": {
"_doc": {
"properties": {
"user": {"type": "keyword"}
}
}
}
}
'
// 模擬資料4個,分別是1-10,2-9,3-8,4-12(_id-likes)
curl -X POST "localhost:9200/twitter/_doc/1" -H 'Content-Type: application/json' -d'
{
"user": "kimchy1",
"likes": 10,
// 使用postman自帶的時間戳變數進行賦值
"post_date": {{$timestamp}},
"message" : "trying out Elasticsearch"
}
'
// 欄位摺疊搜尋
curl -X GET "localhost:9200/twitter/_search" -H 'Content-Type: application/json' -d'
{
"query": {
"match": {
"message": "elasticsearch"
}
},
// 使用user欄位摺疊結果,注意這個欄位的型別為keyword
"collapse" : {
"field" : "user"
},
// 選出點贊數的頂部文件
"sort": ["likes"],
// 定義第一個摺疊結果的偏移量
"from": 1
}
'
響應中的總命中數表示沒有摺疊的匹配文件的數量,不同組的總數是未知的。用於摺疊的欄位必須是啟用doc_values
keyword
或numeric
欄位(這點一定要注意,我在測試的時候就是在這裡遭坑了)。最終的結果為:
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 4,
"max_score": null,
// 命中4個結果,但偏移量為1,顯示後面3個,即第一個命中的likes為8的缺省了
"hits": [
{
"_index": "twitter",
"_type": "_doc",
"_id": "2",
"_score": null,
"_source": {
"user": "kimchy2",
"likes": 9,
"post_date": 1542197883,
"message": "trying out Elasticsearch"
},
// 將命中的結果摺疊為user欄位
"fields": {
"user": [
"kimchy2"
]
},
"sort": [
9
]
},
{
"_index": "twitter",
"_type": "_doc",
"_id": "1",
"_score": null,
"_source": {
"user": "kimchy1",
"likes": 10,
"post_date": 1542197868,
"message": "trying out Elasticsearch"
},
"fields": {
"user": [
"kimchy1"
]
},
"sort": [
10
]
},
{
"_index": "twitter",
"_type": "_doc",
"_id": "4",
"_score": null,
"_source": {
"user": "kimchy4",
"likes": 12,
"post_date": 1542197912,
"message": "trying out Elasticsearch"
},
"fields": {
"user": [
"kimchy4"
]
},
"sort": [
12
]
}
]
}
}
摺疊僅適用於頂部匹配,不會影響聚合。
展開摺疊的結果
上述是摺疊命中的結果,同時也可以使用inner_hits
引數展開每個摺疊的頂部匹配,比如:
curl -X GET "localhost:9200/twitter/_search" -H 'Content-Type: application/json' -d'
{
"query": {
"match": {
"message": "elasticsearch"
}
},
"collapse": {
// 使用user欄位摺疊結果集
"field": "user",
"inner_hits": {
// 用於響應中內部命中部分的名稱
"name": "kimchy4",
// 每個摺疊檢索的inner_hits數量
"size": 1,
// 指定每組文件的排序方式
"sort": [{"post_date": "asc"}]
},
// 指定允許每組檢索 inner_hits 的併發請求數量
"max_concurrent_group_searches": 2
},
"sort": ["likes"]
}
'
size
引數含義未知(待處理)
ES也支援在每個摺疊命中請求多個內部命中inner_hits
,這個功能可以在想要獲取摺疊命中中多個表示時使用:
curl -X GET "localhost:9200/twitter/_search" -H 'Content-Type: application/json' -d'
{
"query": {
"match": {
"message": "elasticsearch"
}
},
"collapse" : {
// 使用user欄位摺疊命中結果
"field" : "user",
"inner_hits": [
// 返回3個點贊數最多的推文
{
"name": "most_liked",
"size": 3,
"sort": ["likes"]
},
// 返回3個最近的推文
{
"name": "most_recent",
"size": 3,
"sort": [{ "date": "asc" }]
}
]
},
"sort": ["likes"]
}
'
通過為響應中返回的每個摺疊命中的每個inner_hit
請求傳送附加查詢來完成組的擴充套件,如果有太多組的邏輯與或inner_hit
請求,這可能會導致速度明顯降低。max_concurrent_group_searches
請求引數可以用來控制在這個階段允許的最大併發搜尋請求數量,允許最大併發請求數預設是根據資料節點和預設搜尋執行緒池的大小決定的。
二級摺疊
欄位摺疊也支援二級摺疊、應用於內部命中inner_hits
。下面的栗子是一個查詢每個國家中最高分的推文,每個國家為每個使用者查詢最高分的推文:
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"]}
}
]
}
}
}
},
....
]
}
2. 搜尋後的處理(Search After)
搜尋可以通過使用from
和to
引數進行分頁,但是在頁碼較大時系統開銷會比較大,index.max_result_window
預設是10000,搜尋請求使用堆的記憶體和時間和from
+size
的大小成比例的,它們的和越大,系統資源消耗的越多。滾動Scroll介面更加適合用來作深層次的滾動(相對高效一點),但是滾動上下文也是有消耗的,所以也不推薦使用滾動Scroll來作面對使用者的實時查詢。search_after
引數通過提供一個活動給游標(live cursor)繞過上述的問題,這個想法是使用前一頁的結果來幫助下一個頁面的檢索。假設獲取第一頁的搜尋結果:
curl -X GET "localhost:9200/twitter/_search" -H 'Content-Type: application/json' -d'
{
"size": 10,
"query": {
"match" : {
"title" : "elasticsearch"
}
},
"sort": [
{"date": "asc"},
// _id欄位啟用了doc_values的副本
{"tie_breaker_id": "asc"}
]
}
'
每個文件中值唯一的欄位可以被用作排序說明的重要元素(tiebreaker,暫先理解為重要元素),否則具有相同排序元素值的文件排序就會成為未定義甚至可能導致丟失或出現重複的結果。每個文件都有一個唯一的_id
欄位值,但並不推薦使用_id
值直接作為tiebreaker。文件值(doc value)是禁止用來作為這個欄位的值,因為以文件值作為排序的標準將會在記憶體中載入大量的資料。取而代之的是,建議複製(客戶端或一組攝取處理器)_id
欄位的值到另一個欄位中(文件值啟用了這個欄位並使用這個新欄位作為排序的決勝局,即tiebreaker)。
上述請求的結果包含了一個數組(包含每個文件的排序值的陣列sort values
),sort values
可以用來和search_after
引數結合在任何結果文件集之後開始返回結果,比如可以使用最後一個文件排序值sort values
並將它傳遞給search_after
去獲取下一頁的結果:
curl -X GET "localhost:9200/twitter/_search" -H 'Content-Type: application/json' -d'
{
"size": 10,
"query": {
"match" : {
"title" : "elasticsearch"
}
},
"search_after": [1463538857, "654323"],
"sort": [
{"date": "asc"},
{"tie_breaker_id": "asc"}
]
}
'
注:當使用search_after
引數時,from
引數必須設定為0或-1。
search_after
不是解決自由跳到一個隨機頁面而是為了滾動許多並行查詢操作,這和scroll
很相似但又不同,search_after
引數是無狀態的,它總是對最新版本的搜尋器來解決,因此排序的順序可能會在走的過程(during a walk)中改變(這取決於索引的更新和刪除)。