连载中 10/20

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

2026-08-15 · 2009 阅读 · 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)

热门推荐

连载中 11/22

主从搭建实操:从零配出一主两从

光讲原理不过瘾?手把手搭一主两从:my.cnf 六个参数、复制账号、GTID、CHANGE REPLICATION SOURCE TO、SHOW REPLICA STATUS 验收,附翻车排查清单。

#MySQL#主从复制#GTID#主从搭建#高可用
2026-05-07 · 10101 阅读 · 0 评论 · 0 赞
连载中 16/22

连接池:HikariCP 参数与连接风暴

连接池不是越大越好:8 核机器配 1000 连接反而更慢的数学原理,HikariCP 四个必调参数,maxLifetime 与 wait_timeout 的隐形陷阱。

#MySQL#连接池#HikariCP#maxLifetime#连接风暴
2026-05-10 · 9873 阅读 · 0 评论 · 0 赞
连载中 4/16

缓存穿透:恶意 ID 打穿 MySQL 的四道防线

请求的数据在缓存和数据库里都不存在时,缓存形同虚设。聊聊参数校验、空值缓存、布隆过滤器、限流熔断四道防线的原理与组合打法。

#Redis#缓存穿透#布隆过滤器#高可用
2026-05-16 · 9294 阅读 · 21 评论 · 287 赞