深度分页问题大致可以分为两类
- 随机深度分页:随机跳转页面
- 滚动深度分页:只能一页一页往下查询
from/size
假设我们现在要从分片数 = 10中,查询from = 5000 size = 10的数据集(假设为页面请求第501页),那么需要的数据是多少呢?
5010 * 10 = 50100 条数据,哇塞,我就查10条数据,你为什么要拿这么数据,干嘛呢,咋不上天呢!
ES其实也不想,但是我们知道ES 数据分布在每个shard中,每个shard分布一部分数据,它并不知道数据是如何排序的,因此只有通过协调节点进行以下操作query/fetch:
(1)协调节点发送请求到shard分布的数据节点,请求数据
(2)每个分片在本地执行查询,并使用本地的Term/Document Frequency信息进行打分,添加结果到大小为from + size的本地有序优先队列中。
(3)每个分片返回个自己优先队列中所有文档的ID和排序值给协调节点,协调节点合并这些值到自己的优先队列中,产生一个全局排序后的列表(注:这里只先返回_id标识和排序值用于排序,不返回整个数据,避免网络开销)
(4)协调节点根据排序后的列表获取相应的数据进行二次请求(注:这次是根据10个文档_id进行GET操作)
(5)获得数据进行返回
从上我们可以看出,在这里ES已经做了一部分优化了
es 目前支持最大的 skip 值是 max_result_window ,默认 为 10000 。也就是当 from + size > max_result_window 时,es 将返回错误
Scroll
使用scroll,每次只能获取一页的内容,然后会返回一个scrollid,根据scrollid可以不断地获取下一页的内容,所以scroll并不适用于有跳页的情景。但是在真正的使用场景中,第10000条数据已经是很后面的数据了,可以“折衷”一下,不提供跳转页面功能,只能下一页的翻页。
Scroll方式通过一次查询请求后维护一个临时的索引快照的search context,此后的增删查改操作并不会影响这个快照数据信息,后续的查询只需要根据游标去取数据,直到结果集中返回的 hits 字段为空,就表示遍历结束。效率比较高。在5.x之后,还可以通过slice分片来实现并行导出。
它的缺点就是维护一个search context需要占用很多资源,而且在快照建立之后数据变化如删除和更新操作是不能被感知到的,所以不能够用于实时和高并发的场景。
search_after
上述的 scroll search 的方式,官方的建议并不是用于实时的请求,因为每一个 scroll_id 不仅会占用大量的资源(特别是排序的请求),而且是生成的历史快照,对于数据的变更不会反映到快照上。这种方式往往用于非实时处理大量数据的情况,比如要进行数据迁移或者索引变更之类的。那么在实时情况下如果处理深度分页的问题呢?es 给出了 search_after 的方式,这是在 >= 5.0 版本才提供的功能。
searchAfter的方式通过维护一个实时游标来避免scroll的缺点,它可以用于实时请求和高并发场景。
它的缺点是不能够随机跳转分页,只能是一页一页的向后翻,并且需要至少指定一个唯一不重复字段来排序(注:每个文档具有一个唯一值的字段应该用作排序规范的仲裁器。否则,具有相同排序值的文档的排序顺序将是未定义的。建议的方法是使用字段_id,它肯定包含每个文档的一个唯一值)。
此外还有一个与scorll的不同之处是searchAfter的读取数据的顺序会受索引的更新和删除影响而scroll不会,因为scroll读取的并不是不可变的快照,而是依赖于上一页最后一条数据,所以无法跳页请求,用于滚动请求,于scroll类似,不同之处在于它使无状态的。
简而言之就是:ES 通过唯一值字段维护了一个sort排序数据,下一次请求可以根据sort排序值来获取下一页数据






还没有评论,来说两句吧...