前言
我們在實際工作中,有很多分頁的需求,商品分頁、訂單分頁等,在MySQL中我們可以使用limit
,那麼在Elasticsearch中我們可以使用什麼呢?
ES 分頁搜尋一般有三種方案,from + size、search after、scroll api,這三種方案分別有自己的優缺點,下面將進行分別介紹。
使用的資料是kibana中的kibana_sample_data_flights
。
from + size
這是ES分頁中最常用的一種方式,與MySQL類似,from指定起始位置,size指定返回的文件數。
GET kibana_sample_data_flights/_search
{
"from": 10,
"size": 2,
"query": {
"match": {
"DestWeather": "Sunny"
}
},
"sort": [
{
"timestamp": {
"order": "asc"
}
}
]
}
這個例子中查詢航班中,目的地的天氣是晴朗的,並且按時間進行排序。
使用簡單,且預設的深度分頁限制是1萬,from + size 大於 10000會報錯,可以通過index.max_result_window
引數進行修改。
{
"error": {
"root_cause": [
{
"type": "query_phase_execution_exception",
"reason": "Result window is too large, from + size must be less than or equal to: [10000] but was [10001]. See the scroll api for a more efficient way to request large data sets. This limit can be set by changing the [index.max_result_window] index level setting."
}
],
"type": "search_phase_execution_exception",
"reason": "all shards failed",
"phase": "query",
"grouped": true,
"failed_shards": [
{
"shard": 0,
"index": "kibana_sample_data_flights",
"node": "YRQNOSQqS-GgSo1TSzlC8A",
"reason": {
"type": "query_phase_execution_exception",
"reason": "Result window is too large, from + size must be less than or equal to: [10000] but was [10001]. See the scroll api for a more efficient way to request large data sets. This limit can be set by changing the [index.max_result_window] index level setting."
}
}
]
},
"status": 500
}
這種分頁方式,在分散式的環境下的深度分頁是有效能問題的,一般不建議用這種方式做深度分頁,可以用下面將要介紹的兩種方式。
理解為什麼深度分頁是有問題的,我們可以假設在一個有 5 個主分片的索引中搜尋。 當我們請求結果的第一頁(結果從 1 到 10 ),每一個分片產生前 10 的結果,並且返回給協調節點 ,協調節點對 50 個結果排序得到全部結果的前 10 個。
現在假設我們請求第 1000 頁,結果從 10001 到 10010 。所有都以相同的方式工作除了每個分片不得不產生前10010個結果以外。 然後協調節點對全部 50050 個結果排序最後丟棄掉這些結果中的 50040 個結果。
可以看到,在分散式系統中,對結果排序的成本隨分頁的深度成指數上升。
search after
search after 利用實時有遊標來幫我們解決實時滾動的問題。第一次搜尋時需要指定 sort,並且保證值是唯一的,可以通過加入 _id 保證唯一性。
GET kibana_sample_data_flights/_search
{
"size": 2,
"query": {
"match": {
"DestWeather": "Sunny"
}
},
"sort": [
{
"timestamp": {
"order": "asc"
},
"_id": {
"order": "desc"
}
}
]
}
在返回的結果中,最後一個文件有類似下面的資料,由於我們排序用的是兩個欄位,返回的是兩個值。
"sort" : [
1614561419000,
"6FxZJXgBE6QbUWetnarH"
]
第二次搜尋,帶上這個sort的資訊即可,如下
GET kibana_sample_data_flights/_search
{
"size": 2,
"query": {
"match": {
"DestWeather": "Sunny"
}
},
"sort": [
{
"timestamp": {
"order": "asc"
},
"_id": {
"order": "desc"
}
}
],
"search_after": [
1614561419000,
"6FxZJXgBE6QbUWetnarH"
]
}
scroll api
建立一個快照,有新的資料寫入以後,無法被查到。每次查詢後,輸入上一次的 scroll_id。目前官方已經不推薦使用這個API了,使用search_after
即可。
GET kibana_sample_data_flights/_search?scroll=1m
{
"size": 2,
"query": {
"match": {
"DestWeather": "Sunny"
}
},
"sort": [
{
"timestamp": {
"order": "asc"
},
"_id": {
"order": "desc"
}
}
]
}
在返回的資料中,有一個_scroll_id
欄位,下次搜尋的時候帶上這個資料,並且使用下面的查詢語句。
POST _search/scroll
{
"scroll" : "1m",
"scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAA6UWWVJRTk9TUXFTLUdnU28xVFN6bEM4QQ=="
}
上面的scroll
指定搜尋上下文保留的時間,1m代表1分鐘,還有其他時間可以選擇,有d、h、m、s等,分別代表天、時、分鐘、秒。
搜尋上下文有過期自動刪除,但如果自己知道什麼時候該刪,可以自己手動刪除,減少資源佔用。
DELETE /_search/scroll
{
"scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAA6UWWVJRTk9TUXFTLUdnU28xVFN6bEM4QQ=="
}
總結
from + size 的優點是簡單,缺點是在深度分頁的場景下系統開銷比較大。
search after 可以實時高效的進行分頁查詢,但是它只能做下一頁這樣的查詢場景,不能隨機的指定頁數查詢。
scroll api 方案也很高效,但是它基於快照,不能用在實時性高的業務場景,且官方已不建議使用。