es深度分頁查詢
前言
近期在做新的專案時,使用了ElasticSearch作為資料的儲存和查詢。接到了一個比較噁心的需求,需要對es進行分頁查詢,單次查詢一萬條,最多需要查詢十次。當時也沒想太多,需求評審時並沒有及時反駁,既然掉坑裡了,那就想辦法爬出來吧!
es的分頁
1)from+size淺分頁
我們當時有點想當然了,以為from+size就可以搞定(業務程式碼寫多的後果)。實際測試的時候,發現記憶體消耗特別大,而且速度也很一般。ES的查詢機制如下:
假設我們的ES有三個節點,當分頁查詢請求過來時,如果落到node1節點,那麼node1節點將會向node2和node3傳送同樣的查詢請求,每個節點將topN的文件返回(這裡只返回文件的id以及打分排序的欄位,減少資料傳輸),node1會對三個節點的所有文件(3*N個)進行排序,取topN後再根據文件的id到對應的節點上查詢整個文件資料,最後返回客戶端。
而對於分頁查詢,比如from=10000,szie=10000,其實每個節點需要查詢from+size=20000條資料,排序之後擷取後10000條資料。當我們進行深度分頁,比如查詢第十頁資料時,每個節點需要查詢10*size=10W條資料,這個太恐怖了。而且預設情況下,當from+size大於10000時,查詢會丟擲一個異常,ES2.0後有一個max_result_window屬性的設定,預設值是10000,也就是from+size的最大限度。當然你可以修改這個值作為臨時的應對策略,不過治標不治本,產品也只會變本加厲!
2)scroll查詢
ES支援scroll滾屏查詢,有興趣的同學可以瞭解一下,網上相關的文件不少。不過根據ES官網的描述,scroll查詢是很耗效能的方式,不建議在實時查詢中運用。摘抄自官網:
3)search_after查詢
search_after是ES5.0及之後版本提供的新特性,search_after有點類似scroll,但是和scroll又不一樣,它提供一個活動的遊標,通過上一次查詢最後一條資料來進行下一次查詢。
比如第一次查詢如下:
GET zm/recall/_search { "query": { "match_all": {} }, "sort": [ { "lastModifyTime": { "order": "desc" } } ], "size": 10 }
這裡根據更新時間進行排序,拿到的結果如下:
{ "_index": "zmrecall", "_type": "recall", "_id": "60310505115909", "_score": null, "_source": { "userId": 60310505115909, "score": 1, "city": [ 276 ], "sex": 1, "age": 29, "lastModifyTime": 1545037514 }, "sort": [ 1545037514 ] }
注意到返回結果中有一個sort欄位,所以下一次查詢的時候,只需要將本次查詢最後一條資料中的排序欄位加入查詢即可:
GET zm/recall/_search { "query": { "match_all": {} }, "sort": [ { "lastModifyTime": { "order": "desc" } } ], "search_after": [1545037514],//這個值與上次查詢最後一條資料的sort值一致,支援多個 "size": 10 }
這裡需要說明一下,使用search_after查詢需要將from設定為0或-1,當然你也可以不寫
另外在ES6.5的文件中有這樣一句話需要注意一下:
大致的意思就是,如果search_after中的關鍵字為654,那麼654323的文件也會被搜尋到,所以在選擇search_after的排序欄位時需要謹慎,可以使用比如文件的id或者時間戳等
另外,search_after並不是隨機的查詢某一頁資料,而是並行的滾屏查詢;search_after的查詢順序會在更新和刪除時發生變化,也就是說支援實時的資料查詢
總結
在使用一個工具之前,一定要對其內部的基本流程有一個簡單的瞭解,否則出現問題時不知從何入手。search_after相比較上面的淺分頁以及scroll滾屏查詢會有很大的效能提升,推薦大家使用!
最後附上ES的官方文件:https://www.elastic.co/guide/en/elasticsearch/reference/6.5/search-request-search-after.html
另外在csdn上也看到一個部落格,講的很不錯:https://blog.csdn.net/ctwy291314/article/details/82754652