java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Elasticsearch深度分页

Elasticsearch深度分页问题的解决方案详解

作者:尽兴-

ES 是分布式搜索引擎,数据分布在多个分片上,本文将深入探讨Elasticsearch深度分页问题及其解决方案,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下

一、深度分页:是什么?为什么会出现问题?

1.1 什么是深度分页

1.2 ES 分页的底层执行机制(from + size)

ES 是分布式搜索引擎,数据分布在多个分片上。标准分页(from + size)的执行流程如下:

  1. 请求分发​:协调节点将查询广播至所有分片;
  2. 分片执行​:每个分片独立查询并返回 from + size 条记录 到内存;
  3. 结果汇总​:协调节点收集所有分片的结果(共 N × (from + size) 条);
  4. 二次排序 + 裁剪​:全局排序后,仅保留 [from, from + size) 区间的数据返回。

核心痛点​:当 from = 10000, size = 100 时,每个分片需加载 10100 条数据,协调节点需对 所有分片的 10100 条 进行全局排序——最终却只返回 ​100 条​!资源浪费极其严重。

1.3 ES 的保护机制:max_result_window

为防止 OOM,ES 默认设置:

index.max_result_window = 10000

from + size ≤ 10000,否则抛出异常。

示例:每页 20 条 → 最多翻到第 ​500 页​;

可通过以下方式修改(​不推荐随意调大​):

PUT /your_index/_settings
{
  "index.max_result_window": 20000
}

1.4 为什么不能简单调大max_result_window?

调大阈值只是 ​**“掩盖问题”**​,而非解决问题:

二、ES 三大分页方式深度对比

维度from + sizescroll APIsearch_after
性能❌ 深度翻页性能差⚠️ 中等(快照开销)✅ 高性能
翻页能力✅ 支持随机跳页 ❌ 受限于 10000❌ 仅向后翻页 ✅ 支持全量遍历❌ 仅向后翻页 ✅ 无限深度
实时性✅ 实时❌​非实时​(快照)✅​近实时​(PIT 轻量视图)
资源占用❌ 高(深度时)⚠️ 中(上下文驻留内存)✅ 低
ES 官方推荐基础场景❌ES7+ 已不推荐✅ES7.10+ 主推方案
使用复杂度✅ 简单⚠️ 需管理 scroll_id⚠️ 需管理 PIT+ 排序值

2.1from + size:基础但危险

语法示例

GET /index/_search
{
  "query": { "match_all": {} },
  "from": 0,
  "size": 10
}

适用场景

限制

2.2scrollAPI:全量遍历的“老方案”

核心原理

使用流程

首次查询​(带 scroll 参数):

GET /index/_search?scroll=5m
{
  "query": { ... },
  "size": 100
}

后续翻页​:

POST /_search/scroll
{
  "scroll": "5m",
  "scroll_id": "xxx"
}

手动清理​(重要!):

DELETE /_search/scroll
{ "scroll_id": "xxx" }

适用场景

缺陷

2.3search_after:官方主推的深度分页方案 

核心优势

关键要求

使用流程

创建 PIT(Point In Time)

POST /index/_pit?keep_alive=5m

→ 返回 pit_id

首次查询

GET /_search
{
  "query": { "term": { "status": "active" } },
  "pit": { "id": "pit_id", "keep_alive": "1m" },
  "size": 20,
  "sort": [
    { "timestamp": "asc" },
    { "_id": "asc" }  // ← 唯一决胜字段!
  ]
}

后续翻页(使用上一页最后一条的排序值)

GET /_search
{
  "query": { "term": { "status": "active" } },
  "pit": { "id": "pit_id", "keep_alive": "5m" },
  "size": 20,
  "sort": [
    { "timestamp": "asc" },
    { "_id": "asc" }
  ],
  "search_after": [1678901234567, "doc_999"]  // ← 上一页最后一条的值
}

释放 PIT(可选但推荐)

DELETE /_pit
{ "id": "pit_id" }

适用场景

三、深度分页的根本解决策略

策略 1:产品设计上规避深度分页(最优解)

最好的优化,是不让问题发生。

策略 2:按业务场景精准选型

业务场景推荐方案原因
PC 搜索 / 后台管理(需跳页)from + size支持随机跳页,且控制在 10000 内
数据导出 / 批量处理scroll全量遍历,无需实时
APP 下拉 / 超长列表search_after高性能、无限深度、近实时

策略 3:用search_after实现高性能深度分页

若必须深度分页,请严格遵循以下最佳实践:

  1. 排序字段必须含唯一决胜字段​(如 _id);
  2. PIT 过期时间合理设置​(如 1~5 分钟),可在每次请求中续期;
  3. 单次 size 控制在 20~100​,平衡性能与请求次数;
  4. 查询结束后主动删除 PIT​,避免资源泄漏。

策略 4:集群级辅助优化

四、总结与核心建议

核心结论

  1. from + size ≠ 深度分页方案 —— 仅适用于 ≤10000 条的随机翻页;
  2. scroll 已过时 —— 仅用于非实时全量处理;
  3. search_after 是未来 —— ES 7.10+ 官方主推,性能与一致性兼得;
  4. 产品设计 > 技术调优 —— 限制用户行为是最高效解法。

落地建议

到此这篇关于Elasticsearch深度分页问题的解决方案详解的文章就介绍到这了,更多相关Elasticsearch深度分页内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文