连载中 10/20

深分页:翻到第 100 页的代价

2026-08-15 · 2003 阅读 · 0 评论 · 0 赞

Result window is too large

搜索页齐活后,运营提了个朴实的需求:把搜索结果翻到后面几页看看有没有漏网之鱼。老王把 from 从 0 拨到 10000,页面直接报错:Result window is too large, from + size must be less than or equal to: 10000。老王第一反应是把默认限制调大,查完原理后庆幸自己没动——这个报错不是 bug,是 ES 在救他的命

from 加 size 越翻越贵的根源

回想第 3 篇的世界观:索引拆了 3 个主分片,查询时协调节点把请求发给每个分片。翻页时发生的事情是:

GET /product/_search  { "from": 10000, "size": 10 }
协调节点要求:每个分片都取回前 10010 条
3 个分片 = 30030 条数据在网络里飞
协调节点内存里归并排序 30030 条,再扔掉前 10000 条
最后只返回 10 条

成本随页码线性放大:翻第 1 页每分片取 10 条,翻第 1001 页每分片取 10010 条。而用户只拿到 10 条。翻得越深,浪费越多,协调节点的堆内存先遭殃。max_result_window 默认 10000 就是给这条链路设的闸。MySQL 深分页 limit 100000, 10 也是同款问题,读者可以回忆 MySQL 系列里的对应篇目,病根一模一样:归并取回一大截,只用一小截。

search_after:翻页不回头

思路反转:既然每次从头数到第 N 条太贵,那就记住上一页最后一条的排序值,下一页直接从它后面接着捞:

GET /product/_search
{
  "size": 10,
  "sort": [ { "sales": "desc" }, { "_id": "asc" } ],
  "search_after": [ 8620, "p1024" ]
}

search_after 里的两个值,就是上一页第 10 条文档的销量和 ID。每个分片只需取到「比这个值更靠后」的少量数据,成本与页码无关,翻到多深都一样快。两个细节决定成败:排序必须带唯一 tie-breaker(销量 8620 的商品可能有一百件,补上 _id 才能唯一定位断点);只支持连续翻页,不能跳页——所以适合「下一页」按钮和无限滚动流,不适合页码条。

scroll 与 PIT:全量导出的两代方案

如果是导出全量数据这种「一口气走完」的场景,用 scroll:第一次查询拍一个快照,后续带着 scroll_id 翻页,数据始终来自同一份快照,不受写入影响。但 scroll 维护快照有成本,且视图会过期,官方已经不推荐它做实时翻页

现在的推荐组合是 PIT 加 search_after:先开一个 point in time 拿到 pit_id,相当于轻量级快照,再在这个视图里连续 search_after,兼顾一致性与性能。

方案适用限制
from + size浅翻页,前几页成本随页码放大,默认封顶 10000
search_after连续翻页、无限滚动不能跳页,排序要带唯一键
scroll离线全量导出快照有维护成本,不推荐实时用
PIT + search_after实时深翻页的官方推荐要先开 PIT,多一次交互

老王最后的产品方案很干脆:用户侧保留页码条,但限制最多翻 100 页——反正数据显示九成用户翻不过三页;运营的全量核查改用 PIT 加 search_after 的导出任务。深分页需求消失,问题就地终结。

小结

深分页一句话:from 加 size 是给浅翻页设计的,深了就用游标:连续翻 search_after,全量走 PIT 或 scroll,跳页需求先问产品要不要真做。报错不是限制你,是提醒你换工具。

翻页搞定了,运营的新需求又来了:按品牌、价格段、销量区间做统计报表。查文档是 ES 的老本行,做统计它也是把好手。下一篇讲聚合:分桶、嵌套与指标,把搜索页变成数据分析页

503

10 年全栈工程师 · 503咖啡馆主理人

#Elasticsearch#深分页#search_after#scroll#PIT

评论 (0)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 5 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 13 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞