5月27日 23:46
Elasticsearch 深度分页是怎么产生的?有哪些解决方案?
Elasticsearch 使用 from + size 做分页时,翻到靠后的页面会越来越慢,甚至直接报错。这个现象叫深度分页问题,是 ES 面试的高频考点。
深度分页是怎么产生的
ES 的分页查询由协调节点协调:假设 from=10000, size=10,协调节点会向每个分片请求 from + size = 10010 条数据,在内存中合并排序后只取最后 10 条返回。分片越多,需要合并的数据量越大,内存占用和延迟随页码深度指数上升。
ES 默认通过 index.max_result_window(默认值 10000)限制 from + size 的上限,超过直接抛异常。这不是 bug,而是保护机制。
核心问题就一句话:协调节点必须持有所有分片的前 N 条数据才能做全局排序,翻页越深,N 越大。
解决方案一:search_after(推荐)
search_after 是官方推荐的实时深分页方案。原理是用上一页最后一条文档的排序值作为游标,下一页直接从该位置向后查,不需要跳过前面所有数据。
首次请求:
jsonGET /my_index/_search { "size": 10, "sort": [ { "timestamp": "desc" }, { "_id": "asc" } ] }
返回结果中每条文档都带有 sort 值,取最后一条:
json"sort": ["2025-06-15T10:30:00.000Z", "abc123"]
后续请求:
jsonGET /my_index/_search { "size": 10, "search_after": ["2025-06-15T10:30:00.000Z", "abc123"], "sort": [ { "timestamp": "desc" }, { "_id": "asc" } ] }
要点:
- 排序字段必须全局唯一(推荐
timestamp + _id组合),否则游标定位不准 - 只能向后翻页,不能跳页
- 实时性:每次查询都反映最新数据
- ES 7.10+ 引入 PIT(Point in Time)配合 search_after 使用,可保证翻页期间索引数据一致
PIT 用法:
json// 1. 先创建 PIT POST /my_index/_pit?keep_alive=1m // 返回 pit_id // 2. 带 PIT 查询 GET /_search { "size": 10, "pit": { "id": "pit_id值", "keep_alive": "1m" }, "sort": [{ "timestamp": "desc" }, { "_id": "asc" }] } // 3. 后续请求同时带 pit 和 search_after GET /_search { "size": 10, "pit": { "id": "pit_id值", "keep_alive": "1m" }, "search_after": ["2025-06-15T10:30:00.000Z", "abc123"], "sort": [{ "timestamp": "desc" }, { "_id": "asc" }] }
解决方案二:scroll
scroll 创建一个数据快照上下文,按批次遍历全部结果,适合数据导出、全量迁移等离线场景。
json// 初始化 GET /my_index/_search?scroll=1m { "size": 1000, "query": { "match_all": {} } } // 后续请求 GET /_search/scroll { "scroll": "1m", "scroll_id": "上一次返回的scroll_id" } // 用完务必清理 DELETE /_search/scroll { "scroll_id": "scroll_id值" }
要点:
scroll参数控制上下文存活时间,建议设分钟级(如1m),用完必须删除,否则占内存- 快照语义:遍历期间看不到数据变更,不适合实时查询
- ES 7.10+ 官方建议新项目用 PIT + search_after 替代 scroll
三种方案对比
| 方案 | 能否跳页 | 实时性 | 性能 | 适用场景 |
|---|---|---|---|---|
| from + size | 能 | 实时 | 深页差 | 前 10000 条内的随机翻页 |
| search_after | 不能 | 实时 | 恒定 | 在线深分页、无限滚动加载 |
| scroll | 不能 | 快照 | 恒定 | 数据导出、批量迁移 |
常见追问
Q1: 为什么不直接调大 max_result_window?
调大只是掩盖问题。from=50000 时协调节点仍要合并所有分片的前 50010 条数据,内存和 CPU 开销不会消失,只是从报错变成慢查询,最后还是会 OOM。
Q2: search_after 在数据插入后游标会失效吗?
不会失效,但可能重复或遗漏。新增文档如果排序值落在已翻过的范围内,不会出现;如果落在未翻过的范围内,会出现。配合 PIT 使用可以冻结索引视图,彻底解决一致性问题。
Q3: 生产环境怎么选?
- 前端分页跳转(第1页、第5页、第50页):from + size,配合业务限制最大页码
- 无限滚动加载 / 加载更多:search_after
- 后台数据导出:scroll,或 PIT + search_after