关于 Elasticsearch 分页查询,这几个问题经常被问到
问题1:想请问下,一次性获取索引上的某个字段的所有值(100 万左右),除了把 max_result_window 调大 ,还有没有啥方法?
问题2:关于 es 的分页,每次拿 20 条展示在前台,然后点击下一页,在查询后面的20条数据,应该要怎么写?
问题3:From+size、Scroll、search_after 的本质区别和应用场景分别是什么?
From + size 分页查询
默认返回前10个匹配的匹配项。其中:
● from:未指定,默认值是 0,注意不是1,代表当前页返回数据的起始值。
● size:未指定,默认值是 10,代表当前页返回数据的条数。
如下指定条件查询和排序:
{
"query": {
"bool": {
"must": [
{
"term": {
"business_tag_id": "3"
}
}
],
"must_not": [],
"should": []
}
},
"from": 0,
"size": 10,
"sort": [],
"aggs": {}
}
共返回 10 条数据。其中:
● from + size 两个参数定义了结果页面显示数据的内容。
From + size 查询优点:
● 支持随机翻页。
From + size 查询缺点:
● 受制于 max_result_window 设置,不能无限制翻页。
● 存在深度翻页问题,越往后翻页越慢。
From + size 查询适用场景:
● 非常适合小型数据集或者大数据集返回 Top N(N <= 10000)结果集的业务场景。
● 类似主流 PC 搜索引擎(谷歌、bing、百度、360、sogou等)支持随机跳转分页的业务场景。
深度翻页不推荐使用 From + size:
● Elasticsearch 会限制最大分页数,避免大数据量的召回导致性能低下。
● Elasticsearch 的 max_result_window 默认值是:10000。也就意味着:如果每页有 10 条数据,会最大翻页至 1000 页。
不推荐使用 from + size 做深度分页查询的核心原因:
● 搜索请求通常跨越多个分片,每个分片必须将其请求的命中内容以及任何先前页面的命中内容加载到内存中。
● 对于翻页较深的页面或大量结果,这些操作会显著增加内存和 CPU 使用率,从而导致性能下降或节点故障。
search_after 查询
search_after 查询本质:使用前一页中的一组排序值来检索匹配的下一页。
前置条件:使用 search_after 要求后续的多个请求返回与第一次查询相同的排序结果序列。也就是说,即便在后续翻页的过程中,可能会有新数据写入等操作,但这些操作不会对原有结果集构成影响。
如何实现呢?
可以创建一个时间点 Point In Time(PIT)保障搜索过程中保留特定事件点的索引状态。
Point In Time(PIT)是 Elasticsearch 7.10 版本之后才有的新特性。
PIT的本质:存储索引数据状态的轻量级视图。
如下示例能很好的解读 PIT 视图的内涵。
# 创建 PIT
POST kibana_sample_data_logs/_pit?keep_alive=1m
# 获取数据量 14074
POST kibana_sample_data_logs/_count
# 新增一条数据
POST kibana_sample_data_logs/_doc/14075
{
"test":"just testing"
}
# 数据总量为 14075
POST kibana_sample_data_logs/_count
# 查询PIT,数据依然是14074,说明走的是之前时间点的视图的统计。
POST /_search
{
"track_total_hits": true,
"query": {
"match_all": {}
},
"pit": {
"id": "48myAwEXa2liYW5hX3NhbXBsZV9kYXRhX2xvZ3MWM2hGWXpxLXFSSGlfSmZIaXJWN0dxUQAWdG1TOWFMTF9UdTZHdVZDYmhoWUljZwAAAAAAAAEN3RZGOFJCMGVrZVNndTk3U1I0SG81V3R3AAEWM2hGWXpxLXFSSGlfSmZIaXJWN0dxUQAA"
}
}
有了 PIT,search_after 的后续查询都是基于 PIT 视图进行,能有效保障数据的一致性。
search_after 分页查询可以简单概括为如下几个步骤。
步骤 1:创建 PIT 视图,这是前置条件不能省。
# Step 1: 创建 PIT
POST kibana_sample_data_logs/_pit?keep_alive=5m
返回结果如下:
{
"id" : "48myAwEXa2liYW5hX3NhbXBsZV9kYXRhX2xvZ3MWM2hGWXpxLXFSSGlfSmZIaXJWN0dxUQAWdG1TOWFMTF9UdTZHdVZDYmhoWUljZwAAAAAAAAEg5RZGOFJCMGVrZVNndTk3U1I0SG81V3R3AAEWM2hGWXpxLXFSSGlfSmZIaXJWN0dxUQAA"
}
keep_alive=5m,类似scroll的参数,代表视图保留时间是 5 分钟,超过 5 分钟执行会报错如下:
"type" : "search_context_missing_exception",
"reason" : "No search context found for id [91600]"
步骤 2:创建基础查询语句,这里要设置翻页的条件。
# Step 2: 创建基础查询
GET /_search
{
"size":10,
"query": {
"match" : {
"host" : "elastic"
}
},
"pit": {
"id": "48myAwEXa2liYW5hX3NhbXBsZV9kYXRhX2xvZ3MWM2hGWXpxLXFSSGlfSmZIaXJWN0dxUQAWdG1TOWFMTF9UdTZHdVZDYmhoWUljZwAAAAAAAAEg5RZGOFJCMGVrZVNndTk3U1I0SG81V3R3AAEWM2hGWXpxLXFSSGlfSmZIaXJWN0dxUQAA",
"keep_alive": "1m"
},
"sort": [
{"response.keyword": "asc"}
]
}
● 设置了PIT,检索时候就不需要再指定索引。
● id 是基于步骤1 返回的 id 值。
● 排序 sort 指的是:按照哪个关键字排序。
在每个返回文档的最后,会有两个结果值,如下所示:
"sort" : [
"200",
4
]
● 其中,“200”就是我们指定的排序方式:基于 {“response.keyword”: “asc”} 升序排列。
而 4 代表什么含义呢?
● 4 代表隐含的排序值,是基于_shard_doc 的升序排序方式。
官方文档把这种隐含的字段叫做:tiebreaker (决胜字段),tiebreaker 等价于_shard_doc。
tiebreaker 本质含义:每个文档的唯一值,确保分页不会丢失或者分页结果数据出现重复(相同页重复或跨页重复)。
步骤3:实现后续翻页。
# step 3 : 开始翻页
GET /_search
{
"size": 10,
"query": {
"match" : {
"host" : "elastic"
}
},
"pit": {
"id": "48myAwEXa2liYW5hX3NhbXBsZV9kYXRhX2xvZ3MWM2hGWXpxLXFSSGlfSmZIaXJWN0dxUQAWdG1TOWFMTF9UdTZHdVZDYmhoWUljZwAAAAAAAAEg5RZGOFJCMGVrZVNndTk3U1I0SG81V3R3AAEWM2hGWXpxLXFSSGlfSmZIaXJWN0dxUQAA",
"keep_alive": "1m"
},
"sort": [
{"response.keyword": "asc"}
],
"search_after": [
"200",
4
]
}
后续翻页都需要借助 search_after 指定前一页的最后一个文档的 sort 字段值。
如下代码所示:
"search_after": [
"200",
4
]
显然,search_after 查询仅支持向后翻页。
search_after 优点:
● 不严格受制于 max_result_window,可以无限制往后翻页。
ps:不严格含义:单次请求值不能超过 max_result_window;但总翻页结果集可以超过。
search_after 缺点:
● 只支持向后翻页,不支持随机翻页。
search_after 适用场景:
● 类似:今日头条分页搜索 https://m.toutiao.com/search;不支持随机翻页,更适合手机端应用的场景。
Scroll 遍历查询
相比于 From + size 和 search_after 返回一页数据,Scroll API 可用于从单个搜索请求中检索大量结果(甚至所有结果),其方式与传统数据库中游标(cursor)类似。
如果把 From + size 和 search_after 两种请求看做近实时的请求处理方式,那么 scroll 滚动遍历查询显然是非实时的。数据量大的时候,响应时间可能会比较长。
scroll 核心执行步骤如下:
步骤 1:指定检索语句同时设置 scroll 上下文保留时间。
实际上,scroll 已默认包含了 search_after 的PIT 的视图或快照功能。
从 Scroll 请求返回的结果反映了发出初始搜索请求时索引的状态,类似在那一个时刻做了快照。随后对文档的更改(写入、更新或删除)只会影响以后的搜索请求。
POST kibana_sample_data_logs/_search?scroll=3m
{
"size": 100,
"query": {
"match": {
"host": "elastic"
}
}
}
步骤 2:向后翻页继续获取数据,直到没有要返回的结果为止。
POST _search/scroll
{
"scroll" : "3m",
"scroll_id":"FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFkY4UkIwZWtlU2d1OTdTUjRIbzVXdHcAAAAAAAGmkBZ0bVM5YUxMX1R1Nkd1VkNiaGhZSWNn"
}
scroll_id 值是步骤 1 返回的结果值。
scroll 查询优点:
● 支持全量遍历。
ps:单次遍历的 size 值也不能超过 max_result_window 大小。
scroll 查询缺点:
● 响应时间非实时。
● 保留上下文需要足够的堆内存空间。
scroll 查询适用场景:
● 全量或数据量很大时遍历结果数据,而非分页查询。
● 官方文档强调:不再建议使用scroll API进行深度分页。如果要分页检索超过 Top 10,000+ 结果时,推荐使用:PIT + search_after。
小结
● From+ size:需要随机跳转不同分页(类似主流搜索引擎)、Top 10000 条数据之内分页显示场景。
● search_after:仅需要向后翻页的场景及超过Top 10000 数据需要分页场景。
● Scroll:需要遍历全量数据场景 。
● max_result_window:调大治标不治本,不建议调过大。
● PIT:本质是视图。
什么是聚合分析
聚合分析是值ElasticSearch除搜索功能外提供的针对ElasticSearch数据做统计分析的功能
● 功能丰富:提供Bucket,Metric,Pipeline等多种分析方式,可以满足大部分的分析需求
● 实时性高:所有的计算结果都是及时返回的,而Hadoop等大数据系统一般都是T+1的。
聚合分类
名称 | 作用 | 对比Mysql |
---|---|---|
分桶类型 (Bucket) | 满足特定条件的文档的集合 | 类似GROUP BY语法 |
指标分析类型(Metric) | 计算最大值,最小值,平均值等 | 类似 COUNT 、 SUM() 、 MAX() 等统计方法 |
管道分析类型(Pipeline) | 对聚合结果进行二次分析 | |
矩阵分析类型(Matrix) | 支持对多个字段的操作并提供一个结果矩阵 |
Bucket聚合分析
按照Bucket的分桶策略,常见的Bucket聚合分析如下:
策略 | 描述 |
---|---|
Terms | 最简单策略,如果是text类型,则按照分词后的结果分桶 |
Range | 按照指定数值的范围来设定分桶规则 |
Date Range | 通过指定日期的范围来设定分桶规则 |
Histogram | 直方图,以固定间隔的策略来分割数据 |
Date Histogram | 针对日期的直方图或者柱状图,是时序数据分析中常用的聚合分析类型 |
Terms:
● 最简单策略,如果是text类型,则按照分词后的结果分桶
{
"size":0,//hits返回的数量,默认20条
"aggs": {
"可自定义分组名": {
"terms": {
"field": "指定字段",
"size": 3// 查询的数量
}
}
}
}
Range:
● 按照指定数值的范围来设定分桶规则
{
"size":0,//hits返回的数量,默认20条
"aggs": {
"可自定义分组名": {
"range": {
"field": "字段",
"ranges": [
{
"from": 10, // 大于等于
"to": 20 // 小于
},
{
"from": 20, // 大于等于
"to": 30 // 小于
},
...
]
}
}
}
}
Date Range:
● 通过指定日期的范围来设定分桶规则
{
"size":0,//hits返回的数量,默认20条
"aggs": {
"可自定义分组名": {
"date_range": {
"field": "字段",
"ranges": [
{
"from": "begin", //格式为:yyyy-MM-dd HH:mm:ss
"to": "end" // 格式为:yyyy-MM-dd HH:mm:ss
},
{
"from": "now-10d/d", //当前时间减去10天,格式为:yyyy-MM-dd HH:mm:ss
"to": "now" // 当前时间,格式为:yyyy-MM-dd HH:mm:ss
}
]
}
}
}
}
Histogram:
● 直方图,以固定间隔的策略来分割数据
{
"size": 0, //hits返回的数量,默认20条
"aggs": {
"NAME": {
"histogram": {
"field": "字段",
"interval": 10, // 步长
"extended_bounds": {
"min": 10,// 最小值 x >= 10
"max": 40 // 最大值 x <= 40
}
}
}
}
}
Date Histogram:
● 针对日期的直方图或者柱状图,是时序数据分析中常用的聚合分析类型
{
"size": 0,
"aggs": {
"NAME": {
"date_histogram": {
"field": "字段",
"interval": "day",// 维度
"format": "yyyy-MM-dd",//格式化日期
"extended_bounds": {
"min": "最小值",
"max": "最大值"
}
}
}
}
}
interval:值有:year、month、week、day、hour、minute
Metric指标分析
计算最大值,最小值,平均值等,类似 COUNT 、 SUM() 、 MAX() 等统计方法。
单值分析
最大和最小:
● max:返回数值类字段的最大值
● min: 返回数值类字段的最小值
{
"size": 0,
"aggs": {
"自定义分组名": {
"max/min": {
"field": "字段"
}
}
}
}
平均值、求和:
● avg:返回数值类字段的平均值
● sum:返回数值类字段的总和
去重:
● cardinality: 是指不同数值的个数,类似SQL中的distinct count概念
多值分析
Stats:
● 返回一系列数值类型的统计值,包含min,max,avg,sum和count,被分析的字段只能是数字类型
Extended Stats:
● 对stats的扩展,包含了更多的统计数据,如方差,标准差等
top_hits:
● 一般用于分桶后获取桶内最匹配的顶部文档列表,即详情数据
聚合附加条件
filter:为聚合分析设定过滤条件。
聚合后排序
可以使用自带的关键数据进行排序,如下:
● _count: 文档数
● _key: 按照key值排序