1. 程式人生 > 其它 >elasticsearch深度分頁問題

elasticsearch深度分頁問題

一、深度分頁方式from + size

es 預設採用的分頁方式是 from+ size 的形式,在深度分頁的情況下,這種使用方式效率是非常低的,比如我們執行如下查詢

1 GET /student/student/_search
2 {
3   "query":{
4     "match_all": {}
5   },
6   "from":5000,
7   "size":10
8 }

意味著 es 需要在各個分片上匹配排序並得到5010條資料,協調節點拿到這些資料再進行排序等處理,然後結果集中取最後10條資料返回。

我們會發現這樣的深度分頁將會使得效率非常低,因為我只需要查詢10條資料,而es則需要執行from+size條資料然後處理後返回。

其次:es為了效能,限制了我們分頁的深度,es目前支援的最大的 max_result_window = 10000;也就是說我們不能分頁到10000條資料以上。

例如:

from + size <= 10000所以這個分頁深度依然能夠執行。

繼續看上圖,當size + from > 10000;es查詢失敗,並且提示

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

有關請求大資料集的更有效方法,請參閱滾動api。這個限制可以通過改變[索引]來設定。哦呵,原來es給我們提供了另外的一個API scroll。難道這個 scroll 能解決深度分頁問題?

二、深度分頁之scroll

在es中如果我們分頁要請求大資料集或者一次請求要獲取較大的資料集,scroll都是一個非常好的解決方案。

使用scroll滾動搜尋,可以先搜尋一批資料,然後下次再搜尋一批資料,以此類推,直到搜尋出全部的資料來scroll搜尋會在第一次搜尋的時候,儲存一個當時的檢視快照,之後只會基於該舊的檢視快照提供資料搜尋,如果這個期間資料變更,是不會讓使用者看到的。每次傳送scroll請求,我們還需要指定一個scroll引數,指定一個時間視窗,每次搜尋請求只要在這個時間視窗內能完成就可以了。

一個滾屏搜尋允許我們做一個初始階段搜尋並且持續批量從Elasticsearch里拉取結果直到沒有結果剩下。這有點像傳統資料庫裡的cursors(遊標)。

滾屏搜尋會及時製作快照。這個快照不會包含任何在初始階段搜尋請求後對index做的修改。它通過將舊的資料檔案儲存在手邊,所以可以保護index的樣子看起來像搜尋開始時的樣子。這樣將使得我們無法得到使用者最近的更新行為。

scroll的使用很簡單

執行如下curl,每次請求兩條。可以定製 scroll = 5m意味著該視窗過期時間為5分鐘。

1 GET /student/student/_search?scroll=5m
2 {
3   "query": {
4     "match_all": {}
5   },
6   "size": 2
7 }
 1 {
 2   "_scroll_id" : "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAC0YFmllUjV1QTIyU25XMHBTck1XNHpFWUEAAAAAAAAtGRZpZVI1dUEyMlNuVzBwU3JNVzR6RVlBAAAAAAAALRsWaWVSNXVBMjJTblcwcFNyTVc0ekVZQQAAAAAAAC0aFmllUjV1QTIyU25XMHBTck1XNHpFWUEAAAAAAAAtHBZpZVI1dUEyMlNuVzBwU3JNVzR6RVlB",
 3   "took" : 0,
 4   "timed_out" : false,
 5   "_shards" : {
 6     "total" : 5,
 7     "successful" : 5,
 8     "skipped" : 0,
 9     "failed" : 0
10   },
11   "hits" : {
12     "total" : 6,
13     "max_score" : 1.0,
14     "hits" : [
15       {
16         "_index" : "student",
17         "_type" : "student",
18         "_id" : "5",
19         "_score" : 1.0,
20         "_source" : {
21           "name" : "fucheng",
22           "age" : 23,
23           "class" : "2-3"
24         }
25       },
26       {
27         "_index" : "student",
28         "_type" : "student",
29         "_id" : "2",
30         "_score" : 1.0,
31         "_source" : {
32           "name" : "xiaoming",
33           "age" : 25,
34           "class" : "2-1"
35         }
36       }
37     ]
38   }
39 }

在返回結果中,有一個很重要的

_scroll_id

在後面的請求中我們都要帶著這個 scroll_id 去請求。

現在student這個索引中共有6條資料,id分別為 1, 2, 3, 4, 5, 6。當我們使用 scroll 查詢第4次的時候,返回結果應該為kong。這時我們就知道已經結果集已經匹配完了。

繼續執行3次結果如下三圖所示。

1 GET /_search/scroll
2 {
3   "scroll":"5m",
4   "scroll_id":"DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAC0YFmllUjV1QTIyU25XMHBTck1XNHpFWUEAAAAAAAAtGRZpZVI1dUEyMlNuVzBwU3JNVzR6RVlBAAAAAAAALRsWaWVSNXVBMjJTblcwcFNyTVc0ekVZQQAAAAAAAC0aFmllUjV1QTIyU25XMHBTck1XNHpFWUEAAAAAAAAtHBZpZVI1dUEyMlNuVzBwU3JNVzR6RVlB"
5 }

由結果集我們可以發現最終確實分別得到了正確的結果集,並且正確的終止了scroll。

三、search_after

from + size的分頁方式雖然是最靈活的分頁方式,但是當分頁深度達到一定程度將會產生深度分頁的問題。scroll能夠解決深度分頁的問題,但是其無法實現實時查詢,即當scroll_id生成後無法查詢到之後資料的變更,因為其底層原理是生成資料的快照。這時 search_after應運而生。其是在es-5.X之後才提供的。

search_after 是一種假分頁方式,根據上一頁的最後一條資料來確定下一頁的位置,同時在分頁請求的過程中,如果有索引資料的增刪改查,這些變更也會實時的反映到遊標上。為了找到每一頁最後一條資料,每個文件必須有一個全域性唯一值,官方推薦使用 _uid 作為全域性唯一值,但是隻要能表示其唯一性就可以。

為了演示,我們需要給上文中的student索引增加一個uid欄位表示其唯一性。

執行如下查詢:

 1 GET /student/student/_search
 2 {
 3   "query":{
 4     "match_all": {}
 5   },
 6   "size":2,
 7   "sort":[
 8     {
 9       "uid": "desc"
10     }  
11   ]
12 }

結果集:

View Code

下一次分頁,需要將上述分頁結果集的最後一條資料的值帶上。

 1 GET /student/student/_search
 2 {
 3   "query":{
 4     "match_all": {}
 5   },
 6   "size":2,
 7   "search_after":[1005],
 8   "sort":[
 9     {
10       "uid": "desc"
11     }  
12   ]
13 }

這樣我們就使用search_after方式實現了分頁查詢。

四、三種分頁方式比較

分頁方式 效能 優點 缺點 場景
from + size 靈活性好,實現簡單 深度分頁問題 資料量比較小,能容忍深度分頁問題
scroll 解決了深度分頁問題

無法反應資料的實時性(快照版本)

維護成本高,需要維護一個 scroll_id

海量資料的匯出(比如筆者剛遇到的將es中20w的資料匯入到excel)

需要查詢海量結果集的資料

search_after

效能最好

不存在深度分頁問題

能夠反映資料的實時變更

實現複雜,需要有一個全域性唯一的欄位

連續分頁的實現會比較複雜,因為每一次查詢都需要上次查詢的結果

海量資料的分頁