前言
1、from size,深度分頁或者size特別大的情況,會出deep pagination問題;且es的自保機制max_result_window也會阻預設的查詢。
2、scroll雖然能夠解決from size帶來的問題,但是由於它代表的是某個時刻的snapshot,不適合做實時查詢;且由於scroll后接超時時間,頻繁地發起scroll請求,也會出現一系列問題。
此時,search_after恰巧能夠解決scroll的非實時取值問題。
form&size / scroll / search_after 性能比較
分別分頁獲取【1 - 10】【49000 - 49010】【 99000 - 99010】范圍各10條數據(前提10w條),性能大致是這樣:
note:該數據並非博主本人測試,是公司wiki里負責es的同事的實驗結果。
由此可知,超級深的分頁,使用search_after最為合適了,from&size方式,列表查詢已經夠了(一般人的操作部分查看第20頁之后的數據),導出列表可以使用scroll。
對於三者的原理:
(1) from / size : 該查詢的實現原理類似於mysql中的limit,比如查詢第10001條數據,那么需要將前面的10000條都拿出來,進行過濾,最終才得到數據。(性能較差,實現簡單,適用於少量數據,數據量不超過10w)。
(2) scroll:該查詢實現類似於消息消費的機制,首次查詢的時候會在內存中保存一個歷史快照以及游標(scroll_id),記錄當前消息查詢的終止位置,下次查詢的時候將基於游標進行消費(性能良好,維護成本高,在游標失效前,不會更新數據,不夠靈活,一旦游標創建size就不可改變,適用於大量數據導出或者索引重建)
(3) search_after: 性能優秀,類似於優化后的分頁查詢,歷史條件過濾掉數據。
from + size 淺分頁
"淺"分頁可以理解為簡單意義上的分頁。它的原理很簡單,就是查詢前20條數據,然后截斷前10條,只返回10-20的數據。這樣其實白白浪費了前10條的查詢。
GET test_dev/_search { "query": { "bool": { "filter": [ { "term": { "age": 28 } } ] } }, "size": 10, "from": 20, "sort": [ { "timestamp": { "order": "desc" }, "_id": { "order": "desc" } } ] }
其中,from定義了目標數據的偏移值,size定義當前返回的數目。默認from為0,size為10,即所有的查詢默認僅僅返回前10條數據。
在這里有必要了解一下from/size的原理:
因為es是基於分片的,假設有5個分片,from=100,size=10。則會根據排序規則從5個分片中各取回100條數據數據,然后匯總成500條數據后選擇最后面的10條數據。
做過測試,越往后的分頁,執行的效率越低。總體上會隨着from的增加,消耗時間也會增加。而且數據量越大,就越明顯!
scroll 深分頁
from+size查詢在10000-50000條數據(1000到5000頁)以內的時候還是可以的,但是如果數據過多的話,就會出現深分頁問題。
為了解決上面的問題,elasticsearch提出了一個scroll滾動的方式。
scroll 類似於sql中的cursor,使用scroll,每次只能獲取一頁的內容,然后會返回一個scroll_id。根據返回的這個scroll_id可以不斷地獲取下一頁的內容,所以scroll並不適用於有跳頁的情景。
GET test_dev/_search?scroll=5m { "query": { "bool": { "filter": [ { "term": { "age": 28 } } ] } }, "size": 10, "from": 0, "sort": [ { "timestamp": { "order": "desc" }, "_id": { "order": "desc" } } ] }
scroll=5m表示設置scroll_id保留5分鍾可用。
使用scroll必須要將from設置為0。
size決定后面每次調用_search搜索返回的數量
然后我們可以通過數據返回的_scroll_id讀取下一頁內容,每次請求將會讀取下10條數據,直到數據讀取完畢或者scroll_id保留時間截止:
GET _search/scroll { "scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAJZ9Fnk1d......", "scroll": "5m" }
注意:請求的接口不再使用索引名了,而是 _search/scroll,其中GET和POST方法都可以使用。
scroll刪除
根據官方文檔的說法,scroll的搜索上下文會在scroll的保留時間截止后自動清除,但是我們知道scroll是非常消耗資源的,所以一個建議就是當不需要了scroll數據的時候,盡可能快的把scroll_id顯式刪除掉。
清除指定的scroll_id:
DELETE _search/scroll/DnF1ZXJ5VGhlbkZldGNo.....
清除所有的scroll:
DELETE _search/scroll/_all
search_after 深分頁
scroll 的方式,官方的建議不用於實時的請求(一般用於數據導出),因為每一個 scroll_id 不僅會占用大量的資源,而且會生成歷史快照,對於數據的變更不會反映到快照上。
search_after 分頁的方式是根據上一頁的最后一條數據來確定下一頁的位置,同時在分頁請求的過程中,如果有索引數據的增刪改查,這些變更也會實時的反映到游標上。但是需要注意,因為每一頁的數據依賴於上一頁最后一條數據,所以無法跳頁請求。
為了找到每一頁最后一條數據,每個文檔必須有一個全局唯一值,官方推薦使用 _uid 作為全局唯一值,其實使用業務層的 id 也可以。
GET test_dev/_search { "query": { "bool": { "filter": [ { "term": { "age": 28 } } ] } }, "size": 20, "from": 0, "sort": [ { "timestamp": { "order": "desc" }, "_id": { "order": "desc" } } ] }
- 使用search_after必須要設置from=0。
- 這里我使用timestamp和_id作為唯一值排序。
- 我們在返回的最后一條數據里拿到sort屬性的值傳入到search_after。
使用sort返回的值搜索下一頁:
GET test_dev/_search { "query": { "bool": { "filter": [ { "term": { "age": 28 } } ] } }, "size": 10, "from": 0, "search_after": [ 1541495312521, "d0xH6GYBBtbwbQSP0j1A" ], "sort": [ { "timestamp": { "order": "desc" }, "_id": { "order": "desc" } } ] }