一、ES 的 from size(浅分页):
如果需要搜索分页,可以通过from size组合来进行。from表示从第几行开始,size表示查询多少条文档。from默认为0,size默认为10。
1、原理:
- 客户端请求发给某个节点
- 节点转发给个个分片,查询每个分片上的前10条
- 结果返回给节点,整合数据,提取前10条
- 返回给请求客户端
2、分析
例如现有一个索引T,该索引接收到了一个查询请求,查询第3页,页大小100(也就是想要排序后第三页的100条数据),即设置了 from = 300, size =100 参数。
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.from((query.getPageNo() > 0 ? (query.getPageNo() - 1) : 0) * query.getPageSize());
sourceBuilder.size(query.getPageSize());
假设索引T有三个分片,那么索引内部就会分别去三个分片里面,各召回 from + size = 300 + 100 = 400 个文档集合。
注意,这里有三个分片!所以一共召回了 400 * 3 = 1200 个文档到协调节点处。
从各个分片召回文档到内存后,协调节点结合全局信息进行一次排序,获取前400 的文档集;
最后,再基于这个 Top400 的文档集,从第300个文档起 (from) ,往后拿100个文档 (size)
这才是第3页,如果数据多时,我想查第200页的100条数据呢,共召回内存(20000+100)*3=......如果用户突然点到10000页呢....很明显,效率会低,内存也容易溢出。
3、限制
为了保护ES集群,防止单一请求数据集合过大,导致内存溢出,所以size的大小不能超过index.max_result_window这个参数的设置,默认为10,000
(当然可以调整这个参数,但是治标不治本),单一请求数据大小一旦超过该阈值,便会出现如下报错,建议你去使用scroll:
Result window is too large, from + size must be less than or equal to: [10000] but was [500000]. See the scroll api for a more efficient way to request large data sets. This limit can be set by changing the [index.max_result_window] index level setting."
所以from size方式只适合数据量少的浅分页场景,单一请求数据集合小于10000的场景,但是实时分页查询。
二、scroll( 深分页)
1、原理:
scroll查询原理是在第一次查询的时候一次性生成一个快照,根据上一次的查询的id(这是一个base64编码的长字符串)来进行下一次的查询,这个就类似于游标。
2、分析:
因为快照没有更新最新的索引,在这个查询后的任何新索引进来的数据,都不会在这个快照中查询到,所以它不是“实时的”。
但是它相对于from和size,不是查询所有数据然后剔除不要的部分,而是记录一个读取的位置,保证下一次快速继续读取,所以性能高。所以可以用来查询数据量多的场景,甚至全部数据。
遍历时,从这个快照里取数据;
在遍历时候,拿到上一次遍历中的_scroll_id,然后带scroll参数,重复上一次的遍历步骤,直到返回的数据为空,表示遍历完成。
每次都要传参数scroll,刷新搜索结果的缓存时间,不要把缓存的时时间设置太长,占用内存。
3、限制
scroll不支持跳页查询。
使用场景:对实时性要求不高的查询
scroll支持排序,scroll-scan不支持排序,是按照索引顺序返回,可以提高查询效率。
scroll-scan第一次查询只支持返回id,没有结果。