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 是官方推荐的实时深分页方案。原理是用上一页最后一条文档的排序值作为游标,下一页直接从该位置向后查,不需要跳过前面所有数据。

首次请求:

json
GET /my_index/_search { "size": 10, "sort": [ { "timestamp": "desc" }, { "_id": "asc" } ] }

返回结果中每条文档都带有 sort 值,取最后一条:

json
"sort": ["2025-06-15T10:30:00.000Z", "abc123"]

后续请求:

json
GET /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
标签:ElasticSearch