各位老爷好,最近太忙了以至于没有更新给大家道歉了。
问题1. 深度分页相关优化
背景是脚本循环取es20条数据,平常时间段内,es里符合条件的数据不会超过1w, 而大促期间,则会超越1w,与es里的索引配置起重读而导致报错。
尝试一:
- 尝试:尝试使用scroll的方式查询,毕竟是个脚本嘛,实时性要求不高,编码很简单,大家对着api写即可。结果是,我查询1w条之后的10条,没有引发报错,但是查询时间直接飙升到20s+,性能算是差极了,如果脚本用的数据是比较急,那怕是跑完所有数据之后,大促也都过去了。
- 分析:我们分析一下为什么这么慢,因为scroll实际上是将一次深度查询的页码分为多次较小的查询,多次查询的时间累加就是你的总查询次数,我尝试过将size扩大到100,但是查询时间依旧很长,鉴于查询时间因素,我们舍弃这种方案。
- 缺点:另外scroll有个缺点,实际上是在进行scroll查询的时候,es会产生一个当前的快照,scroll的后续查询都是基于这个快照的,如果在查询过程中有新的文档添加进来,你的这次scroll查询是无法召回这个文档的。
- 总结: 如果你对数据的实时性不高,我是指能接受查询期间新增加的数据丢失,并且你有充足的时间,那么深度查询的时候你可以选择scroll的方式查询。
尝试二:
- 尝试:scroll的方式失败后,我尝试在接口调用方进行优化,因为场景里需要的数据没有排序要求,只要把符合条件的文档全部召回即可,所以我在调用方加了排序参数,根据id正序排,一点点取,每次取出数据记录最后一个id,后续查询时增加id作为参数,而es这边直接根据传进来的id过滤掉<id的文档,配合size参数,可以避免深度查询,实时也证明此方式时可以的。
- 分析:此方式可以避免深度查询,我们实际上时记录了一个游标,即id,每次携带id进入es,直接过滤掉<id的文档,这样我们实际上每次都只是查询这个id之后的size条数据。
- 缺点: 改起来比较繁琐,涉及到旧的接口,可能会几率遗漏,导致部分功能失效。
- 总结:实际上我们是模拟了游标查询,id即为游标,而在es侧,我们不再查询出所有的from然后取size,而是直接过滤掉<id的文档,然后取后面的size条数据。
尝试三:
-
尝试:虽然已经有一个解决方案了,但是我们希望有更专业的,所以我们可以使用es里的search_after的方式进行深度查询,具体如何使用,也很简单,大家可以找api对着用即可,其中要注意的就是
1)我们需要保证一个唯一的sort,建议将id加入sort排序中去
2)from需要设置为0或者-1,否则会引发报错。
GET movies/_search
{
"size": 2,
"query": {
"match_all": {}
},
"search_after":[
"1"
],
"sort": [
{
"_id": {
"order": "asc"
}
}
]
}
-
分析:search_after的原理实际上时通过唯一排序定位,将每次要处理的文档都控制为size大小,通过此方式来避免深度分页。
-
缺点:search_after无法进行跳跃分页。。。只能每次往后查询
-
总结:search_after是官方给的深度查询方式,在使用条件都符合的情况下,我们推荐使用此方式,如果有条件限制并且有跳跃查询的话,我推荐使用方案而,因为它比较灵活。
from/size、scroll、search_after三者的比较
- from / size : 该查询的实现原理类似于mysql中的limit,比如查询第10001条数据,那么需要将前面的10000条都拿出来,进行过滤,最终才得到数据。(性能较差,实现简单,适用于少量数据,数据量不超过1w,1w是可配置的,量力而行)。
- scroll:该查询实现类似于消息消费的机制,首次查询的时候会在内存中保存一个历史快照以及游标(scroll_id),记录当前消息查询的终止位置,下次查询的时候将基于游标进行消费(性能良好,维护成本高,在游标失效前,不会更新数据,不够灵活,一旦游标创建size就不可改变,适用于大量数据导出或者索引重建)
- search_after: 性能优秀,类似于优化后的分页查询,历史条件过滤掉数据,不适合存在跳跃查询的场景。