1.背景
12月9日,PIS针对地震多报问题,做了一次紧急需求迭代,然而在上线验证过程中,测试同学使用多台手机接收app push,却并没有按照预期全部接收到地震预警。排查日志,发现如下报错:
该报警是在分页查询ES中查询满足push条件的用户时,由ES报错触发的。从报警信息中不难看出,ES中有对分页大小限制的“阈值”,恰好某次分页查询超出了这个“阈值”,导致报错。
2.原因
带着这个疑问,查阅相关资料:
elasticsearch深度分页问题: https://www.cnblogs.com/hello-shf/p/11543453.html
es 默认采用的分页方式是 from+ size 的形式,在深度分页的情况下,这种使用方式效率是非常低的:
ElasticSearch在分布式系统中的深度分页
理解为什么深度分页是有问题的,我们可以假设在一个有 4 个主分片的索引中搜索。 当我们请求结果的第一页(结果从 1 到 10 ),每一个分片产生前 10 的结果,并且返回给 协调节点 ,协调节点对 40 个结果排序得到全部结果的前 10 个。
现在假设我们请求第 990 页--结果从 990 到 1000 。所有都以相同的方式工作除了每个分片不得不产生前1000个结果以外。 然后协调节点对全部 4000 个结果排序最后丢弃掉这些结果中的 3990 个结果。
可以看到,在分布式系统中,对结果排序的成本随分页的深度成指数上升。这就是 web 搜索引擎对任何查询都不要返回超过 10000 个结果的原因。
[图片上传中...(image-3fed2-1585304297229-0)]
很显然,这次ES报错,也是由于当前的分页窗口查询到第5000条及以后的数据时,数据大小超出了5000这个系统参数配置,导致报错并触发app push失败。
事实也的确是这样的,本次触发报错的模拟地震数据如下:
震中位置 经度:114.72 维度:41.15
预警等级:4.8
震源深度:8
影响用户:51770(条件:烈度大于1)
由于影响用户已经超出了50000,并且max_result_window这个参数配置为50000,当分页查询50000条之后的数据时,ES抛出异常。最直接的影响就是,按照目前的配置,最多能为50000用户发送push...
{
"index": {
"max_result_window": 50000 }
}
3.解决办法
很显然,单纯的调大 max_result_window这个参数并不满足系统的可扩展,可维护性,因为谁也不能保证一次真实的地震影响的用户数量是否在这个范围之内。
ES提供了Search After这种查询方式,有效的解决了上面的问题:
Search After
实时获取下一页文档信息
- 不支持指定页数
- 只能往下翻
假设Size 10,当查询990-1000,它通过唯一排序值定位,将每次要处理的文档数都控制在10。
search_after 是一种假分页方式,根据上一页的最后一条数据来确定下一页的位置,同时在分页请求的过程中,如果有索引数据的增删改查,这些变更也会实时的反映到游标上。为了找到每一页最后一条数据,每个文档必须有一个全局唯一值,官方推荐使用 _uid 作为全局唯一值,但是只要能表示其唯一性就可以。
4.其他
还有一种scrll方式,但是使用的是数据快照,不能满足实时查询的需求,这里不做讨论。
分页方式对比
分页方式 | 性能 | 优点 | 缺点 | 场景 | |
---|---|---|---|---|---|
from + size | 低 | 灵活性好,实现简单 | 深度分页问题 | 数据量比较小,能容忍深度分页问题 | |
scroll | 中 | 解决了深度分页问题 | 无法反应数据的实时性(快照版本)维护成本高,需要维护一个 scroll_id, | 海量数据的导出,需要查询海量结果集的数据 | |
search_after | 高 | 性能最好不存在深度分页问题能够反映数据的实时变更 | 实现复杂,需要有一个全局唯一的字段连续分页的实现会比较复杂,因为每一次查询都需要上次查询的结果 | 海量数据的分页 |