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