一、 要解決的問題
- search命中的記錄特別多,使用from+size分頁,直接觸發了elasticsearch的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": "shirts",
"node": "OBkTpZcTQJ25kmlNZ6xyLg",
"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
}
- 將elasticsearch作為資料庫使用,直接將max_result_window設定一個很大的值,但是資料量大了很影響查詢效能;
二、elasticsearch支援的分頁方式
elasticsearch提供了三種分頁的查詢方式,以支援不同的查詢場景;
from + size
search after
scroll
以下測試使用的是 elasticsearch 6.8
三、 from + size 分頁
from + size是使用最普遍的search分頁方案;
from: 設定要返回第一條記錄的相對位置,預設值為0;
size: 此次search返回的最大記錄數量, 預設值為10;
我們可以直接從第11條記錄開始,返回最多10條記錄;
GET my_store_index/_search
{
"query": {
"match": {
"name": "bj"
}
},
"from": 10,
"size": 10
}
此種分頁方式特點
- 特別適合隨機獲取記錄,實現類似商用搜尋引擎的分頁功能;
- 由於from + size是無狀態的,搜尋的時候,每個分片都需要返回from + size條記錄,最終將所有分片的記錄進行合併再返回size條記錄;
- 受限實現機制,越靠後的記錄查詢效能越差,故elasticsearch預設設定max_result_window=10000,如果from + size超過這個值就會報錯;
四、search after
即使我們將max_result_window調整成一個更大的值,但是當我們命中的結果比較多的時候,使用from + size的分頁效果就會比較差;
elasticsearch提供的search after可以幫助我們解決這個問題;search after可以利用請求中包含的上一頁的資訊來幫助查詢下一頁的記錄;
起始搜尋如下,需要新增sort欄位,並使用id作為排序欄位;這裡的排序欄位需要確保每個document都是不同的,這樣才能確保排序的唯一性;
GET my_store_index/_search
{
"_source": false,
"query": {
"match": {
"name": "bj"
}
},
"size": 10,
"sort": [
{
"id": {
"order": "desc"
}
}
]
}
我們可以看到返回結果中包含了sort欄位,裡邊包含了命中記錄對應的sort的值;
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 25,
"max_score" : null,
"hits" : [
{
"_index" : "my_store_index",
"_type" : "_doc",
"_id" : "f64cf9f4-db2b-4059-bf97-315fe95f233c",
"_score" : null,
"sort" : [
"f64cf9f4-db2b-4059-bf97-315fe95f233c"
]
}
]
}
}
我們使用上一個請求中的sort的值作為以下請求中search_after的引數,來查詢下一頁的資料;
GET my_store_index/_search
{
"_source": false,
"query": {
"match": {
"name": "bj"
}
},
"size": 10,
"search_after":["f64cf9f4-db2b-4059-bf97-315fe95f233c"],
"sort": [
{
"id": {
"order": "desc"
}
}
]
}
此種分頁方式的特點
- 需要指定一個值唯一的排序欄位,通過返回記錄的sort值對應的記錄作為from的記錄;
- 此種分頁查詢方式也是無狀態的,分頁的時候需要依賴search_after引數作為起始點,這樣就可以直接跳過已經獲取過的記錄;
- 可以通過當前時間點最後一條記錄的sort值,同時通過size來控制同時獲取多頁的記錄來實現簡單的向後隨機翻頁;
- 需要記錄已經載入的每頁起始的sort值,可以實現前向的隨機翻頁;
我們分別使用from+size、search after查詢第二個10000條記錄,檢視兩者執行時間,可以發現search after快將近2s;
GET my_store_index/_search
{
"_source":["id"],
"query": {
"match_phrase_prefix": {
"deviceData.content": "us"
}
},
"size": 10000,
"from": 10000
}
{
"took":8212,
"timed_out":false,
"_shards":{
"total":5,
"successful":5,
"skipped":0,
"failed":0
},
"hits":{
"total":29908,
"max_score":97.09149
}
}
GET my_store_index/_search
{
"_source":["id"],
"query": {
"match_phrase_prefix": {
"deviceData.content": "us"
}
},
"size": 10000,
"sort": [
{
"id": {
"order": "desc"
}
}
],
"search_after":["aa877c87-bb08-4fbd-8a51-ed4ebaa57251"]
}
{
"took":6320,
"timed_out":false,
"_shards":{
"total":5,
"successful":5,
"skipped":0,
"failed":0
},
"hits":{
"total":29908,
"max_score":null
}
}
五、scroll
elasticsearch提供的scroll可以實現一個請求返回所有命中記錄,我們可以使用類似關聯式資料庫中的遊標的方式來獲取命中的記錄; scroll並不是為了實現實時的搜尋請求,更多的是為了處理大量的資料,尤其適合從某一個index進行重新索引;
為了使用scroll,我們需要在url裡通過scroll指定elasticsearch需要保留搜尋結果的時間;
GET my_store_index/_search?scroll=1m
{
"_source": false,
"query": {
"match": {
"name": "bj"
}
},
"size": 2
}
{
"_scroll_id" : "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAb0Fk9Ca1RwWmNUUUoyNWttbE5aNnh5TGcAAAAAAAAG8xZPQmtUcFpjVFFKMjVrbWxOWjZ4eUxnAAAAAAAABvUWT0JrVHBaY1RRSjI1a21sTlo2eHlMZwAAAAAAAAb2Fk9Ca1RwWmNUUUoyNWttbE5aNnh5TGcAAAAAAAAG9xZPQmtUcFpjVFFKMjVrbWxOWjZ4eUxn",
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 25,
"max_score" : 3.5134304,
"hits" : [
]
}
}
我們使用上邊請求返回的_scroll_id來獲取下一頁的資料;
GET _search/scroll
{
"scroll" : "1m",
"scroll_id" : "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAdlFk9Ca1RwWmNUUUoyNWttbE5aNnh5TGcAAAAAAAAHZhZPQmtUcFpjVFFKMjVrbWxOWjZ4eUxnAAAAAAAAB2cWT0JrVHBaY1RRSjI1a21sTlo2eHlMZwAAAAAAAAdoFk9Ca1RwWmNUUUoyNWttbE5aNnh5TGcAAAAAAAAHaRZPQmtUcFpjVFFKMjVrbWxOWjZ4eUxn"
}
此種分頁方式的特點
- 首次查詢通過query string引數指定scroll的時間,elasticsearch會自動生成一個scroll並返回對應的id;
- 此查詢是一種有狀態的查詢,不會實時響應索引變化;
- 每次請求返回的記錄數通過首次請求設定的size引數控制;
- 返回scroll對應的全部記錄之後再查詢就返回空陣列;
- scroll引數控制每次請求之後elasticsearch保留scroll的時間;