Elasticsearch進行深度分頁的詳細指南(避免踩坑+報錯)
一、問題復(fù)現(xiàn):為何查詢會觸發(fā)「Result window is too large」
當(dāng)我們在 Elasticsearch 中使用傳統(tǒng)分頁參數(shù) from 和 size 時,若 from + size > 10000,會直接觸發(fā)如下異常:
{ "error": { "root_cause": [{ "type": "illegal_argument_exception", "reason": "Result window is too large, from + size must be <= 10000" }] } }
??根本原因??:
Elasticsearch 默認(rèn)限制單次查詢返回的文檔總數(shù)不超過 10,000 條(即 index.max_result_window 參數(shù))。當(dāng)進行深度分頁(如查詢第 10001-10100 條數(shù)據(jù))時,協(xié)調(diào)節(jié)點需要從所有分片中??先拉取前 10100 條數(shù)據(jù)??,再進行全局排序和截取,導(dǎo)致內(nèi)存和計算資源爆炸。
二、解決方案對比:哪種方案適合你的場景
方案 | 原理 | 優(yōu)點 | 缺點 | 適用場景 |
---|---|---|---|---|
??調(diào)整 max_result_window?? | 直接修改索引配置增大分頁窗口 | 實現(xiàn)簡單,無需改代碼 | 內(nèi)存風(fēng)險高,僅適合小數(shù)據(jù)量 | 少量數(shù)據(jù)的分頁(≤10萬條) |
Scroll API?? | 通過快照機制保持查詢上下文,分批次拉取數(shù)據(jù) | 支持海量數(shù)據(jù)導(dǎo)出 | 數(shù)據(jù)實時性差,資源消耗大 | 批量導(dǎo)出/離線任務(wù) |
??Search After?? | 基于上一頁最后一個文檔的排序值作為游標(biāo),避免 from 累積 | 性能最優(yōu),支持實時分頁 | 必須定義全局排序字段 | C端實時分頁(如列表頁瀏覽) |
三、方案詳解與代碼實現(xiàn)
1. 暴力擴容法:調(diào)整 max_result_window(不推薦)
??實現(xiàn)步驟??:
# 動態(tài)修改索引配置(需保留原有設(shè)置) PUT /your_index/_settings?preserve_existing=true { "index": { "max_result_window": "20000" # 設(shè)置為更大的值 } }
核心問題??:
- 官方明確警告此操作可能導(dǎo)致 ??OOM(內(nèi)存溢出)?? 和節(jié)點故障
- 深度分頁時,協(xié)調(diào)節(jié)點仍需加載前 N 條數(shù)據(jù)到內(nèi)存,性能呈指數(shù)級下降
- 僅適合臨時測試或數(shù)據(jù)量極小的場景(如后臺管理后臺導(dǎo)出 10 萬條數(shù)據(jù))
2.批量導(dǎo)出法:Scroll API(適合離線場景)
??實現(xiàn)原理??:
通過 scroll 參數(shù)創(chuàng)建快照上下文,后續(xù)請求通過 scroll_id 持續(xù)拉取數(shù)據(jù),避免重復(fù)計算排序。
??Java 代碼示例??:
public JSONArray scrollQuery(JSONObject params) { JSONArray result = new JSONArray(); String scrollId = null; try { // 初始化滾動查詢(保持 10 分鐘快照) SearchRequest searchRequest = new SearchRequest("logs"); SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); sourceBuilder.size(1000); sourceBuilder.query(QueryBuilders.matchAllQuery()); searchRequest.source(sourceBuilder); searchRequest.scroll(TimeValue.timeValueMinutes(10)); // 首次查詢獲取 scroll_id SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT); scrollId = response.getScrollId(); result.addAll(Arrays.asList(response.getHits().getHits())); // 持續(xù)拉取數(shù)據(jù) while (true) { SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId); scrollRequest.scroll(TimeValue.timeValueMinutes(10)); response = client.scroll(scrollRequest, RequestOptions.DEFAULT); if (response.getHits().getHits().length == 0) break; result.addAll(Arrays.asList(response.getHits().getHits())); scrollId = response.getScrollId(); } } finally { // 清理上下文(必須操作) if (scrollId != null) { ClearScrollRequest clearRequest = new ClearScrollRequest(); clearRequest.addScrollId(scrollId); client.clearScroll(clearRequest, RequestOptions.DEFAULT); } } return result; }
??關(guān)鍵問題??:
- 每次滾動需維護 scroll_id,內(nèi)存占用隨數(shù)據(jù)量增長
- 數(shù)據(jù)快照版本可能導(dǎo)致查詢結(jié)果不一致(如文檔被更新或刪除)
3.實時分頁法:Search After(推薦方案)
??實現(xiàn)原理??:
通過記錄上一頁最后一個文檔的排序值(如時間戳或唯一ID),在下一次查詢時直接定位到該位置,??跳過無效數(shù)據(jù)掃描??。
??Java 代碼實現(xiàn)??:
java public JSONObject searchData(JSONObject queryConditionsParam) { int pageSize = queryConditionsParam.getInt("pageSize"); double[] searchAfter = null; // 提取游標(biāo)參數(shù)(上一頁最后一個文檔的排序值) if (queryConditionsParam.containsKey("search_after")) { JSONArray searchAfterArray = queryConditionsParam.getJSONArray("search_after"); searchAfter = searchAfterArray.toDoubleArray(); } SearchRequest searchRequest = new SearchRequest("my_log"); SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); // 關(guān)鍵配置:排序字段必須與 search_after 對應(yīng) sourceBuilder.sort("created_start_time", SortOrder.DESC); if (searchAfter != null) { sourceBuilder.searchAfter(searchAfter); } sourceBuilder.size(pageSize); // 無需設(shè)置 from 參數(shù) // 構(gòu)建查詢條件(示例:按日志ID過濾) BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); boolQueryBuilder.must(QueryBuilders.termQuery("log_id", queryConditionsParam.getInt("log_id"))); // 其他復(fù)雜條件可在此追加... sourceBuilder.query(boolQueryBuilder); searchRequest.source(sourceBuilder); try { SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT); return buildResult(response); // 封裝結(jié)果并返回游標(biāo) } catch (IOException e) { log.error("ES查詢失敗", e); throw new RuntimeException("查詢異常"); } } // 結(jié)果封裝方法:提取游標(biāo)并返回下一頁參數(shù) private JSONObject buildResult(SearchResponse response) { JSONObject result = new JSONObject(); JSONArray hits = new JSONArray(); double[] nextCursor = null; for (SearchHit hit : response.getHits()) { hits.add(new JSONObject(hit.getSourceAsString())); // 提取排序字段值作為下一頁游標(biāo) if (hit.getSortValues().length > 0) { nextCursor = Arrays.stream(hit.getSortValues()) .mapToDouble(Double::valueOf) .toArray(); } } result.put("data", hits); result.put("totalCount", response.getHits().getTotalHits().value); if (nextCursor != null) { result.put("search_after", nextCursor); // 返回游標(biāo)供下次查詢 } return result; }
??性能優(yōu)勢??:
??無深度分頁開銷??: 每次查詢僅獲取當(dāng)前頁數(shù)據(jù),避免全量數(shù)據(jù)掃描
??實時性保障??: 直接訪問最新數(shù)據(jù)快照,不受索引刷新影響
??資源消耗低??: 內(nèi)存占用與分頁大小線性相關(guān),而非與數(shù)據(jù)總量相關(guān)
四、方案選型決策樹
數(shù)據(jù)量 ≤10 萬條?? → 調(diào)整 max_result_window(快速實現(xiàn))
??需要全量導(dǎo)出?? → Scroll API(配合異步任務(wù))
??C端實時交互?? → Search After(最佳實踐)
五、避坑指南
1. 游標(biāo)失效場景??:
數(shù)據(jù)更新或刪除時,可能導(dǎo)致游標(biāo)失效(需結(jié)合業(yè)務(wù)場景評估)
避免在頻繁更新的字段上使用 search_after
2.分頁深度限制??:
即使使用 search_after,仍建議限制最大分頁深度(如最多 1000 頁),防止惡意請求
??3.監(jiān)控與告警??:
通過 Elasticsearch 的 _cat/indices 接口監(jiān)控分頁查詢頻率,設(shè)置閾值告警
六、總結(jié)
方案 | 推薦指數(shù) | 適用階段 |
---|---|---|
調(diào)整 max_result_window | ?☆☆☆☆ | 早期驗證階段 |
Scroll API | ??☆☆☆ | 臨時數(shù)據(jù)遷移/批量導(dǎo)出 |
Search After | ????? | 生產(chǎn)環(huán)境實時分頁 |
??終極建議: 在日志分析、用戶行為追蹤等場景中,結(jié)合 search_after + 時間范圍過濾 + 適當(dāng)?shù)木彺娌呗裕蓪崿F(xiàn)億級數(shù)據(jù)的高效分頁。立即升級你的分頁方案,告別 Result window is too large 報錯!
到此這篇關(guān)于Elasticsearch進行深度分頁的詳細指南(避免踩坑+報錯)的文章就介紹到這了,更多相關(guān)Elasticsearch深度分頁內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
eclipse輸出Hello World的實現(xiàn)方法
這篇文章主要介紹了eclipse輸出Hello World的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11Spring Bean實例的創(chuàng)建及構(gòu)造器的挑選
這篇文章主要介紹了Spring Bean實例的創(chuàng)建及構(gòu)造器的挑選,文中有非常詳細的代碼示例,對正在學(xué)習(xí)java的小伙伴們有很好的幫助,需要的朋友可以參考下2021-04-04圖文講解IDEA中根據(jù)數(shù)據(jù)庫自動生成實體類
這篇文章主要以圖文講解IDEA中根據(jù)數(shù)據(jù)庫自動生成實體類,本文主要以Mysql數(shù)據(jù)庫為例,應(yīng)該會對大家有所幫助,如果有錯誤的地方,還望指正2023-03-03springmvc接收json串,轉(zhuǎn)換為實體類List方法
今天小編就為大家分享一篇springmvc接收json串,轉(zhuǎn)換為實體類List方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08