2、 Elasticsearch 支援的三種分頁查詢方式
From + Size 查詢
Search After 查詢
Scroll 查詢
下面我就三種方式的聯系與差別、優缺點、适用場景等展開進行解讀。
2.1 From + size 分頁查詢
2.1.1 From + size 分頁查詢定義與實戰案例
如下基礎查詢:
GET kibana_sample_data_flights/_search
預設傳回前10個比對的比對項。其中:
from:未指定,預設值是 0,注意不是1,代表目前頁傳回資料的起始值。
size:未指定,預設值是 10,代表目前頁傳回資料的條數。
如下指定條件查詢和排序:
{
"from": 0,
"size":20,
"query": {
"match": {
"DestWeather": "Sunny"
}
},
"sort": [
{
"FlightTimeHour": {
"order": "desc"
}
]
}
共傳回 20 條資料。
其中:from + size 兩個參數定義了結果頁面顯示資料的内容。
2.1.2 From + size 查詢優缺點及适用場景
From + size 查詢優點
支援随機翻頁。
From + size 查詢缺點
受制于 max_result_window 設定,不能無限制翻頁。
存在深度翻頁問題,越往後翻頁越慢。
From + size 查詢适用場景
第一:非常适合小型資料集或者大資料集傳回 Top N(N <= 10000)結果集的業務場景。
第二:類似主流 PC 搜尋引擎(谷歌、bing、百度、360、sogou等)支援随機跳轉分頁的業務場景。
2.1.3 深度翻頁不推薦使用 From + size
Elasticsearch 會限制最大分頁數,避免大資料量的召回導緻性能低下。
Elasticsearch 的 max_result_window 預設值是:10000。也就意味着:如果每頁有 10 條資料,會最大翻頁至 1000 頁。
實際主流搜尋引擎都翻不了那麼多頁,舉例:百度搜尋“上海”,翻到第 76 頁,就無法再往下翻頁了,提示資訊如下截圖所示:

"size":10001
"from": 10001,
"size":10
報錯如下:
"error" : {
"root_cause" : [
{
"type" : "illegal_argument_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."
],
什麼原因?超過了最大視窗的限制,index.max_result_window 預設值為10000。
報錯資訊還同時給出了兩個解決方案:
方案一:大資料集召回資料使用:scroll api。
後面會詳細講解。
方案二:調大 index.max_result_window 預設值。
PUT kibana_sample_data_flights/_settings
"index.max_result_window":50000
官方建議:避免過度使用 from 和 size 來分頁或一次請求太多結果。
不推薦使用 from + size 做深度分頁查詢的核心原因:
搜尋請求通常跨越多個分片,每個分片必須将其請求的命中内容以及任何先前頁面的命中内容加載到記憶體中。
對于翻頁較深的頁面或大量結果,這些操作會顯著增加記憶體和 CPU 使用率,進而導緻性能下降或節點故障。
什麼意思呢?
"size": 10
共 10 條資料加載到記憶體嗎?不是的!
共:10011 條資料加載到記憶體,然後經過背景處理後傳回了最後 10 條我們想要的資料。
那也就意味着,越往後翻頁(也就是深度翻頁)需要加載的資料量越大,勢必會越耗費 CPU + 記憶體資源,響應也會越慢!
2.2 search_after 查詢
2.2.1 search_after 查詢定義與實戰案例
search_after 查詢本質:使用前一頁中的一組排序值來檢索比對的下一頁。
前置條件:使用 search_after 要求後續的多個請求傳回與第一次查詢相同的排序結果序列。也就是說,即便在後續翻頁的過程中,可能會有新資料寫入等操作,但這些操作不會對原有結果集構成影響。
如何實作呢?
可以建立一個時間點 Point In Time(PIT)保障搜尋過程中保留特定事件點的索引狀态。
Point In Time(PIT)是 Elasticsearch 7.10 版本之後才有的新特性。
PIT的本質:存儲索引資料狀态的輕量級視圖。
如下示例能很好的解讀 PIT 視圖的内涵。
# 建立 PIT
POST kibana_sample_data_logs/_pit?keep_alive=1m
# 擷取資料量 14074
POST kibana_sample_data_logs/_count
# 新增一條資料
POST kibana_sample_data_logs/_doc/14075
"test":"just testing"
# 資料總量為 14075
# 查詢PIT,資料依然是14074,說明走的是之前時間點的視圖的統計。
POST /_search
"track_total_hits": true,
"match_all": {}
},
"pit": {
"id": "48myAwEXa2liYW5hX3NhbXBsZV9kYXRhX2xvZ3MWM2hGWXpxLXFSSGlfSmZIaXJWN0dxUQAWdG1TOWFMTF9UdTZHdVZDYmhoWUljZwAAAAAAAAEN3RZGOFJCMGVrZVNndTk3U1I0SG81V3R3AAEWM2hGWXpxLXFSSGlfSmZIaXJWN0dxUQAA"
}
有了 PIT,search_after 的後續查詢都是基于 PIT 視圖進行,能有效保障資料的一緻性。
search_after 分頁查詢可以簡單概括為如下幾個步驟。
步驟 1:建立 PIT 視圖,這是前置條件不能省。
# Step 1: 建立 PIT
POST kibana_sample_data_logs/_pit?keep_alive=5m
傳回結果如下:
"id" : "48myAwEXa2liYW5hX3NhbXBsZV9kYXRhX2xvZ3MWM2hGWXpxLXFSSGlfSmZIaXJWN0dxUQAWdG1TOWFMTF9UdTZHdVZDYmhoWUljZwAAAAAAAAEg5RZGOFJCMGVrZVNndTk3U1I0SG81V3R3AAEWM2hGWXpxLXFSSGlfSmZIaXJWN0dxUQAA"
keep_alive=5m,類似scroll的參數,代表視圖保留時間是 5 分鐘,超過 5 分鐘執行會報錯如下:
"type" : "search_context_missing_exception",
"reason" : "No search context found for id [91600]"
步驟 2:建立基礎查詢語句,這裡要設定翻頁的條件。
# Step 2: 建立基礎查詢
GET /_search
"size":10,
"match" : {
"host" : "elastic"
"pit": {
"id": "48myAwEXa2liYW5hX3NhbXBsZV9kYXRhX2xvZ3MWM2hGWXpxLXFSSGlfSmZIaXJWN0dxUQAWdG1TOWFMTF9UdTZHdVZDYmhoWUljZwAAAAAAAAEg5RZGOFJCMGVrZVNndTk3U1I0SG81V3R3AAEWM2hGWXpxLXFSSGlfSmZIaXJWN0dxUQAA",
"keep_alive": "1m"
"sort": [
{"response.keyword": "asc"}
設定了PIT,檢索時候就不需要再指定索引。
id 是基于步驟1 傳回的 id 值。
排序 sort 指的是:按照哪個關鍵字排序。
在每個傳回文檔的最後,會有兩個結果值,如下所示:
"sort" : [
"200",
4
]
其中,“200”就是我們指定的排序方式:基于 {"response.keyword": "asc"} 升序排列。
而 4 代表什麼含義呢?
4 代表——隐含的排序值,是基于_shard_doc 的升序排序方式。
官方文檔把這種隐含的字段叫做:tiebreaker (決勝字段),tiebreaker 等價于_shard_doc。
tiebreaker 本質含義:每個文檔的唯一值,確定分頁不會丢失或者分頁結果資料出現重複(相同頁重複或跨頁重複)。
步驟3:實作後續翻頁。
# step 3 : 開始翻頁
"size": 10,
],
"search_after": [
"200",
4
後續翻頁都需要借助 search_after 指定前一頁的最後一個文檔的 sort 字段值。
如下代碼所示:
顯然,search_after 查詢僅支援向後翻頁。
2.2.2 search_after 查詢優缺點及适用場景
search_after 優點
不嚴格受制于 max_result_window,可以無限制往後翻頁。
ps:不嚴格含義:單次請求值不能超過 max_result_window;但總翻頁結果集可以超過。
search_after 缺點
隻支援向後翻頁,不支援随機翻頁。
search_after 适用場景
類似:今日頭條分頁搜尋
https://m.toutiao.com/search不支援随機翻頁,更适合手機端應用的場景。
2.3 Scroll 周遊查詢
2.3.1 Scroll 周遊查詢定義與實戰案例
相比于 From + size 和 search_after 傳回一頁資料,Scroll API 可用于從單個搜尋請求中檢索大量結果(甚至所有結果),其方式與傳統資料庫中遊标(cursor)類似。
如果把 From + size 和 search_after 兩種請求看做近實時的請求處理方式,那麼 scroll 滾動周遊查詢顯然是非實時的。資料量大的時候,響應時間可能會比較長。
scroll 核心執行步驟如下:
步驟 1:指定檢索語句同時設定 scroll 上下文保留時間。
實際上,scroll 已預設包含了 search_after 的PIT 的視圖或快照功能。
從 Scroll 請求傳回的結果反映了發出初始搜尋請求時索引的狀态,類似在那一個時刻做了快照。随後對文檔的更改(寫入、更新或删除)隻會影響以後的搜尋請求。
POST kibana_sample_data_logs/_search?scroll=3m
"size": 100,
"host": "elastic"
步驟 2:向後翻頁繼續擷取資料,直到沒有要傳回的結果為止。
POST _search/scroll
"scroll" : "3m",
"scroll_id":"FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFkY4UkIwZWtlU2d1OTdTUjRIbzVXdHcAAAAAAAGmkBZ0bVM5YUxMX1R1Nkd1VkNiaGhZSWNn"
scroll_id 值是步驟 1 傳回的結果值。
2.3.2 Scroll 周遊查詢優缺點及适用場景
scroll 查詢優點
支援全量周遊。
ps:單次周遊的 size 值也不能超過 max_result_window 大小。
scroll 查詢缺點
響應時間非實時。
保留上下文需要足夠的堆記憶體空間。
scroll 查詢适用場景
全量或資料量很大時周遊結果資料,而非分頁查詢。
官方文檔強調:不再建議使用scroll API進行深度分頁。如果要分頁檢索超過 Top 10,000+ 結果時,推薦使用:PIT + search_after。
3、小結
From+ size:需要随機跳轉不同分頁(類似主流搜尋引擎)、Top 10000 條資料之内分頁顯示場景。
search_after:僅需要向後翻頁的場景及超過Top 10000 資料需要分頁場景。
Scroll:需要周遊全量資料場景 。
max_result_window:調大治标不治本,不建議調過大。
PIT:本質是視圖。
本文說法有不嚴謹的地方,以官方文檔為準。
歡迎大家就自己的分頁實踐進行留言讨論。
參考:
1.
https://coralogix.com/log-analytics-blog/how-to-optimize-your-elasticsearch-queries-using-pagination2.
https://www.javatpoint.com/elasticsearch-pagination3.
https://www.elastic.co/guide/en/elasticsearch/reference/7.12/paginate-search-results.html