Elasticsearch 的 REST APIs:聚合查询

摘要

聚合查询

  • 聚合查询,可以让我们极其方便的实现对索引数据的统计、分析、运算等操作。

  • 基本语法包括以下部分:

    • 查询条件:指定需要聚合的文档,可以使用标准的 Elasticsearch 查询语法,如 term、match、range 等等。
    • 聚合函数:指定要执行的聚合操作,如 sum、avg、min、max、terms、date_histogram 等等。每个聚合命令都会生成一个聚合结果。
    • 聚合嵌套:聚合命令可以嵌套,以便更细粒度地分析数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
GET <index_name>/_search
{
"aggs": {
"<aggs_name>": {
"<agg_type>": {
"field": "<field_name>"
}
}
}
}
# 说明
# aggs:聚合的根节点,固定写法
# aggs_name:聚合函数的名称,自己随意定义
# agg_type:聚合种类,比如是桶聚合(terms)或者是指标聚合(avg、sum、min、max等)
# field_name:字段名称或者叫域名。
  • 聚合的分类

    • Metric Aggregation:—些数学运算,可以对文档字段进行统计分析,类比Mysql中的 min(), max(), sum() 操作。
    • Bucket Aggregation:一些满足特定条件的文档的集合放置到一个桶里,每一个桶关联一个key,类比Mysql中的group by操作。
    • Pipeline Aggregation:对其他的聚合结果进行二次聚合

示例数据准备

  • 我们需要先准备一些数据,才能进行聚合分析。

  • 创建索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# 如果存在先删除
curl -X DELETE -u elastic:123456 -k 'https://127.0.0.1:9200/shopping'

# 创建索引
curl -X PUT -u elastic:123456 -k 'https://127.0.0.1:9200/shopping' \
-H 'Content-Type: application/json' \
-d '{
"settings":{
"number_of_shards":"1",
"number_of_replicas":"2"
},
"mappings":{
"properties":{
"category":{
"type":"keyword"
},
"price":{
"type":"double"
},
"count":{
"type":"integer"
},
"title":{
"type":"text",
"analyzer":"ik_max_word",
"search_analyzer":"ik_smart"
},
"remark":{
"type":"text",
"analyzer":"ik_max_word",
"search_analyzer":"ik_smart"
},
"address": {
"type": "text",
"analyzer": "ik_max_word",
"fields": {
"keyword": {
"type": "keyword"
}
}
},
"tags": {
"type": "keyword"
},
"created_at": {"type": "date", "format": "yyyy-MM-dd HH:mm:ss"}
}
}
}'

# 插入数据
curl -X POST -u elastic:123456 -k 'https://127.0.0.1:9200/_bulk' \
-H 'Content-Type: application/json' \
-d '{ "index": { "_index": "shopping", "_id": "1" } }
{ "category": "electronics", "price": 999.99, "count": 10, "title": "Smartphone X - 128GB", "remark": "This latest Smartphone X comes with a powerful processor and high-resolution camera.", "address": "广州天河公园", "tags": ["latest", "smartphone", "technology"], "created_at": "2025-04-22 03:30:02" }
{ "index": { "_index": "shopping", "_id": "2" } }
{ "category": "fashion", "price": 49.99, "count": 25, "title": "Designer T-shirt", "remark": "Trendy designer T-shirt made with high quality fabric.", "address": "广州荔湾大厦", "tags": ["clothing", "designer", "style"], "created_at": "2025-04-21 12:15:00" }
{ "index": { "_index": "shopping", "_id": "3" } }
{ "category": "home_appliances", "price": 299.99, "count": 5, "title": "Robot Vacuum Cleaner", "remark": "Efficient robot vacuum cleaner with smart navigation.", "address": "广州白云山公园", "tags": ["appliances", "vacuum", "robot"], "created_at": "2025-04-20 08:45:30" }
{ "index": { "_index": "shopping", "_id": "4" } }
{ "category": "books", "price": 19.99, "count": 100, "title": "Inspirational Novel", "remark": "A gripping novel that inspires and motivates.", "address": "321 Book Rd, Shenzhen, China", "tags": ["book", "novel", "inspiration"], "created_at": "2025-04-19 10:05:00" }
{ "index": { "_index": "shopping", "_id": "5" } }
{ "category": "groceries", "price": 3.99, "count": 200, "title": "Organic Apples", "remark": "Fresh and crispy organic apples.", "address": "654 Grocery Ln, Chengdu, China", "tags": ["fruit", "organic", "healthy"], "created_at": "2025-04-18 14:30:45" }
{ "index": { "_index": "shopping", "_id": "6" } }
{ "category": "electronics", "price": 1999.99, "count": 15, "title": "Smartphone X - 256GB", "remark": "This latest Smartphone X comes with a powerful processor and high-resolution camera.", "address": "广州天河公园", "tags": ["latest", "smartphone", "technology"], "created_at": "2025-04-22 03:30:02" }
'

Metric Aggregation(指标聚合)

单值分析︰只输出一个分析结果

  • min, max, avg, sum

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 最大值、最小值、平均值、总和
GET /shopping/_search
{
"size": 0, # 不返回文档,只返回聚合结果
"aggs": {
"max_price": { # 自定义名称,这里返回最大值
"max": { # 指标聚合函数名称
"field": "price" # 指标聚合字段
}
},
"min_price": {
"min": {
"field": "price"
}
},
"avg_price": {
"avg": {
"field": "price"
}
},
"sum_price": {
"sum": {
"field": "price"
}
}
}
}
  • Cardinality(类似distinct Count)

1
2
3
4
5
6
7
8
9
10
11
GET /shopping/_search
{
"size": 0, # 不返回文档,只返回聚合结果
"aggs": {
"cardinality_count": { # 自定义名称,返回去重后的数量
"cardinality": { # 指标聚合函数名称,cardinality表示去重
"field": "category" # 指标聚合字段,去重的字段
}
}
}
}

多值分析:输出多个分析结果

  • stats(统计), extended stats

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
GET /shopping/_search
{
"size": 0,
"aggs": {
"stats_price": {
"stats": { # 指标聚合函数名称,stats表示统计,会返回多个指标
"field": "price"
}
}
}
}
# 结果
{
"took": 15,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 12,
"relation": "eq"
},
"max_score": null,
"hits": [] # 因为设置size 为 0,所以返回的结果为空
},
"aggregations": { # 聚合结果
"stats_price": { # 自定义名称,返回多组聚合结果
"count": 12,
"min": 3.99,
"max": 1999.99,
"avg": 562.1666666666666,
"sum": 6746
}
}
}
  • percentile (百分位), percentile rank

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GET /shopping/_search
{
"size": 0,
"aggs": {
"percentile_price": {
"percentiles": { # 指标聚合函数名称,percentiles表示百分位数聚合计算
"field": "price", # 指标聚合字段
"percents": [1, 5, 25, 50, 75, 95, 99] # 需要计算百分位数的数组
}
}
}
}
# 百分位数是一个统计学概念,用于描述一个给定集合的排序值。例如:
# 第 1 百分位数表示数据中小于这个值的所有数据占总数的 1%。
# 第 50 百分位数(中位数)表示数据中的一半小于这个值,另一半大于这个值。
# 第 99 百分位数表示数据中小于这个值的所有数据占总数的 99%。
# 通过计算这些百分位数,你可以了解 price 字段在整个shopping索引中的分布情况,从而可以用于数据分析、趋势判断等。
  • top hits(排在前面的示例)

1
2
3
4
5
6
7
8
9
10
11
12
GET /shopping/_search
{
"size": 0,
"aggs": {
"top_hits": { # 自定义名称,返回排在前面的示例
"top_hits": { # 指标聚合函数名称,top_hits表示返回排在前面的示例
"sort": [{"price": {"order": "desc"}}], # 排序,按照price字段降序排序
"size": 3 # 只返回前3个
}
}
}
}

Bucket Aggregation(桶聚合)

  • 按照一定的规则,将文档分配到不同的桶中,从而达到分类的目的。

  • ES提供的一些常见的 Bucket Aggregation。

    • terms(词条), range(范围), date_range(日期范围), ip_range(IP范围), missing(缺失), histogram(直方图), date_histogram(日期直方图), geo_distance(地理距离), significant_terms(重要词条), composite(组合)
  • 桶聚合可以用于各种场景,例如:

    • 对数据进行分组统计,比如按照地区、年龄段、性别等字段进行分组统计。
    • 对时间序列数据进行时间段分析,比如按照每小时、每天、每月、每季度、每年等时间段进行分析。
    • 对各种标签信息分类,并统计其数量。

terms(词条)

  • 按照字段的值进行分组,并统计每个组的数量。

  • 示例:按照category字段进行分组,并统计每个组的数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
GET /shopping/_search
{
"size": 0,
"aggs": {
"category_count": { # 自定义名称,返回每个组的数量
"terms": { # 桶聚合函数名称,terms表示按照字段的值进行分组
"field": "category", # 按照category字段进行分组,注意不能是text类型,否则会报错
"size": 10, # 只返回前10个,默认是10
"order": {"_count": "desc"} # 按照数量降序排序,默认是desc,Bucket聚合会统计Bucket内的文档数量,记为_count,并且按照_count降序排序
}
}
}
}

# 结果
{
"took": 15,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 12,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"category_count": {
"doc_count_error_upper_bound": 0, # 错误上界,表示在聚合过程中可能存在一些错误,但错误数量不会超过这个值
"sum_other_doc_count": 0, # 表示在聚合过程中,除了返回的10个bucket,还有其他数量,这个数量就是sum_other_doc_count
"buckets": [ # 返回的bucket
{
"key": "electronics", # 分组的key
"doc_count": 4 # 分组的数量
},
{
"key": "books",
"doc_count": 2
},
{
"key": "fashion",
"doc_count": 2
},
{
"key": "groceries",
"doc_count": 2
},
{
"key": "home_appliances",
"doc_count": 2
}
]
}
}
}
  • 限定聚合范围

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
GET /shopping/_search
{
"query": {
"range": {
"price": {
"gte": 100 # 限定价格范围
}
}
},
"size": 0,
"aggs": {
"category_count": { # 自定义名称,返回每个组的数量
"terms": { # 桶聚合函数名称,terms表示按照字段的值进行分组
"field": "category", # 按照category字段进行分组
"size": 10, # 只返回前10个,默认是10
"order": {"_count": "desc"} # 按照数量降序排序,默认是desc,Bucket聚合会统计Bucket内的文档数量,记为_count,并且按照_count降序排序
}
}
}
}

range(范围)

  • 按照字段的值进行分组,并统计每个组的数量。

  • 示例:按照价格范围进行分组,并统计每个组的数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
GET /shopping/_search
{
"size": 0,
"aggs": {
"price_range": { # 自定义名称,返回每个组的数量
"range": { # 桶聚合函数名称,range表示按照字段的值进行分组
"field": "price", # 按照price字段进行分组
"ranges": [
{"to": 100}, # 小于100
{"from": 100, "to": 200}, # 100-200,包括100,不包括200
{
"key":">=200", # 设定键值,用于区分不同范围
"from":200 # 大于等于200
}
]
}
}
}
}
# 结果
{
"took": 5,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 12,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"price_range": {
"buckets": [
{
"key": "*-100.0",
"to": 100,
"doc_count": 6
},
{
"key": "100.0-200.0",
"from": 100,
"to": 200,
"doc_count": 0
},
{
"key": ">=200",
"from": 200,
"doc_count": 6
}
]
}
}
}

date_range(日期范围)

  • 按照字段的值进行分组,并统计每个组的数量。

  • 示例:按照日期范围进行分组,并统计每个组的数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
GET /shopping/_search
{
"size": 0,
"aggs": {
"date_range": {
"date_range": {
"field": "created_at",
"ranges": [
{"to": "now-3d"}, # 小于当前时间减去3天
{"from": "now-3d","to": "now-1d"}, # 大于当前时间减去3天(包含),小于当前时间减去1天
{"key":"now-1d","from": "now-1d"} # 大于当前时间减去1天(包含),并设置键值
]
}
}
}
}
# 结果
{
"took": 3,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 12,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"date_range": {
"buckets": [
{
"key": "*-2025-04-20 03:14:17",
"to": 1745118857610,
"to_as_string": "2025-04-20 03:14:17",
"doc_count": 4
},
{
"key": "2025-04-20 03:14:17-2025-04-22 03:14:17",
"from": 1745118857610,
"from_as_string": "2025-04-20 03:14:17",
"to": 1745291657610,
"to_as_string": "2025-04-22 03:14:17",
"doc_count": 4
},
{
"key": "now-1d",
"from": 1745291657610,
"from_as_string": "2025-04-22 03:14:17",
"doc_count": 4
}
]
}
}
}

# 日期格式
GET /shopping/_search
{
"size": 0,
"aggs": {
"date_range": {
"date_range": {
"field": "created_at",
"ranges": [
{"to": "2025-04-20 00:00:00"}, # 注意这里的格式必须与字段格式一致
{"from": "2025-04-20 00:00:00","to": "2025-04-22 00:00:00"},
{"key":"2025-04-22 00:00:00","from": "2025-04-22 00:00:00"}
]
}
}
}
}

histogram(直方图)

  • 按照间隔进行分组,并统计每个组的数量。

  • 示例:按照价格的间隔进行分组,并统计每个组的数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
GET /shopping/_search
{
"size": 0,
"aggs": {
"price_histogram": {
"histogram": {
"field": "price",
"interval": 500, # 间隔500
"extended_bounds":{ # 设置边界
"min":0, # 最小值0
"max":2000 # 最大值2000
}
}
}
}
}
# 结果
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 12,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"price_histogram": {
"buckets": [
{
"key": 0,
"doc_count": 8
},
{
"key": 500,
"doc_count": 1
},
{
"key": 1000,
"doc_count": 1
},
{
"key": 1500,
"doc_count": 2
},
{
"key": 2000,
"doc_count": 0
}
]
}
}
}

top_hits(分桶取前N条)

  • 按照字段的值进行分组,并返回每个组的前N条数据。

  • 示例:按照category进行分组,并返回每个组的前3条数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
GET /shopping/_search
{
"size": 0,
"aggs": {
"price_top_hits": { # 自定义名称
"terms": {
"field": "category" # 按照category字段进行分组
},
"aggs": {
"top_hits": { # 自定义名称
"top_hits": { # 返回每个组的前3条数据
"size": 3, # 返回前3条数据
"sort": { # 按照created_at字段进行排序
"created_at": {
"order": "desc" # 降序
}
}
}
}
}
}
}
}

子聚合

  • 子聚合:在聚合函数中,还可以再定义一个聚合函数。

  • 示例:按照category进行分组,并统计价格信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
GET /shopping/_search
{
"size": 0,
"aggs": {
"category_buckets": { # 自定义名称
"terms": {
"field": "category" # 按照category字段进行分组
}
"aggs": { # 定义子聚合
"price_stats": {
"stats": { # 统计价格信息
"field": "price"
}
}
}
}
}
}

# 多层嵌套
# 先按照category进行分组,再按照address进行分组,并统计价格信息
GET /shopping/_search
{
"size":0,
"aggs":{
"category_buckets":{
"terms":{
"field":"category"
},
"aggs":{
"address_buckets":{
"terms":{
"field":"address.keyword"
},
"aggs":{
"price_stats":{
"stats":{
"field":"price"
}
}
}
}
}
}
}
}

Pipeline Aggregation(管道聚合)

  • 支持对聚合分析的结果,再次进行聚合分析。

  • Pipeline 的分析结果会输出到原结果中,根据位置的不同,分为两类:

    • Sibling - 结果和现有分析结果同级
      • Max,min,Avg & Sum Bucket
      • Stats,Extended Status Bucket
      • Percentiles Bucket
    • Parent -结果内嵌到现有的聚合分析结果之中
      • Derivative(求导)
      • Cumultive Sum(累计求和)
      • Moving Function(移动平均值)

Max,min,Avg & Sum Bucket

  • Max,min,Avg & Sum Bucket:对聚合分析的结果进行最大值、最小值、平均值、求和等操作。

  • 示例:按照category进行分组求出商品的平均价格,并找出平均价格最低的分组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
GET /shopping/_search
{
"size":0,
"aggs":{
"category_buckets":{
"terms":{
"field":"category" # 按照category字段进行分组
},
"aggs":{
"price_avg":{
"avg":{ # 求价格平均值
"field":"price"
}
}
}
},
"min_price_in_category":{ # 找出平均价格最低的分组
"min_bucket":{ # 对聚合结果进行最小值操作
"buckets_path":"category_buckets>price_avg" # 指定路径,对category_buckets聚合结果中的price_avg字段进行最小值操作
}
}
}
}

Stats,Extended Status Bucket

  • Stats,Extended Status Bucket:对聚合分析的结果进行统计操作,包括最大值、最小值、平均值、求和、数量等。

  • 示例:按照category进行分组求出商品的平均价格,并对分组结果进行统计操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
GET /shopping/_search
{
"size": 0,
"aggs": {
"category_buckets": {
"terms": {
"field": "category",
"size": 10 # 指定分组数量
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
},
"stats_price_by_job":{
"stats_bucket": { # 对聚合结果进行统计操作
"buckets_path": "category_buckets>avg_price" # 指定路径,对category_buckets聚合结果中的avg_price字段进行统计操作
}
}
}
}

Percentiles Bucket

  • Percentiles Bucket:对聚合分析的结果进行百分比操作,包括百分比、中位数等。

  • 示例:按照category进行分组求出商品的平均价格,并对分组结果进行百分比操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
GET /shopping/_search
{
"size": 0,
"aggs": {
"category_buckets": {
"terms": {
"field": "category",
"size": 10
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
},
"percentiles_price_by_category":{
"percentiles_bucket": { # 对聚合结果进行统计操作
"buckets_path": "category_buckets>avg_price" # 指定路径,对category_buckets聚合结果中的avg_price字段进行统计操作
}
}
}
}

Cumulative Sum

  • Cumulative Sum:对聚合分析的结果进行累计求和操作。

  • 示例: 按照价格进行分组,并统计平均价格信息,对分组结果进行累计求和操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
GET /shopping/_search
{
"size": 0,
"aggs": {
"price_histogram": {
"histogram": {
"field": "price",
"interval": 500, # 间隔500
"extended_bounds":{ # 设置边界
"min":0, # 最小值0
"max":2000 # 最大值2000
}
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
},
"cumulative_sum_price": {
"cumulative_sum": { # 对聚合结果进行累计求和操作
"buckets_path": "avg_price" # 指定路径,对avg_price字段进行累计求和操作
}
}
}
}
}
}

Derivative

  • Derivative:对聚合分析的结果进行求导操作。

  • Derivative 聚合查询是 Elasticsearch 中的一种高级聚合类型,用于计算某个度量值随时间(或其他顺序字段)变化的速率。它通常用于时间序列分析,以揭示度量值的增减趋势。

  • 示例:显示每天的平均价格以及平均价格的变化趋势

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
GET /shopping/_search
{
"size": 0,
"aggs": {
"price_over_time": {
"date_histogram": { # 按照日期进行分组
"field": "created_at", # 指定字段
"calendar_interval": "day" # 指定时间间隔,可用的值:year、quarter、month、week、day、hour、minute、second
},
"aggs": {
"average_price": {
"avg": {
"field": "price"
}
},
"price_derivative": {
"derivative": { # 对聚合结果进行求导操作
"buckets_path": "average_price"
}
}
}
}
}
}

Moving Function

  • Moving Function:对聚合分析的结果进行移动平均值操作。

  • Moving Function 聚合查询是 Elasticsearch 中的一种高级聚合类型,用于计算某个度量值在时间窗口内的移动平均值。它通常用于时间序列分析,以了解度量值的变化趋势。

  • 示例:计算 price 字段的 7 天移动平均值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
GET /shopping/_search
{
"size": 0,
"aggs": {
"price_over_time": {
"date_histogram": {
"field": "created_at",
"calendar_interval": "day"
},
"aggs": {
"daily_avg_price": {
"avg": {
"field": "price"
}
},
"moving_avg_price": {
"moving_fn": { # 对聚合结果进行移动平均值操作
"buckets_path": "daily_avg_price", # 指定路径,对daily_avg_price字段进行移动平均值操作
"script": "MovingFunctions.unweightedAvg(values)", # 指定脚本,计算移动平均值
"window": 7, # 窗口大小,默认为 10
"shift": 0 # 窗口偏移量,默认为 0
}
}
}
}
}
}

ES 聚合性能优化

索引预排序

  • 如果是 Elasticsearch 6.X 之后版本,可以在插入数据时对索引进行预排序,而不是在查询时再对索引进行排序,这将提高范围查询(range query)和排序操作的性能。

  • 但预排序将增加 Elasticsearch 写入的成本,导致大约 40%-50% 的写性能下降,如果应用场景是更关注写性能的业务,开启索引预排序不是一个很好的选择。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PUT /my_index
{
"settings": {
"index":{
"sort.field": "create_time",
"sort.order": "desc"
}
},
"mappings": {
"properties": {
"create_time":{
"type": "date"
}
}
}
}

使用分片请求缓存

  • 聚合语句中,设置:size:0,就会使用分片请求缓存缓存结果。size = 0 的含义是:只返回聚合结果,不返回查询结果。

1
2
3
4
5
6
7
8
9
10
11
GET /my_index/_search
{
"size": 0,
"aggs": {
"remark_agg": {
"terms": {
"field": "remark.keyword"
}
}
}
}

拆分聚合,使聚合并行化

  • Elasticsearch 查询条件中同时有多个条件聚合,默认情况下聚合不是并行运行的。

  • 当为每个聚合提供自己的查询并执行 msearch 时,性能会有显著提升。

  • 因此,在 CPU 资源不是瓶颈的前提下,如果想缩短响应时间,可以将多个聚合拆分为多个查询,借助:msearch 实现并行聚合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#常规的多条件聚合实现
GET /employees/_search
{
"size": 0,
"aggs": {
"job_agg": {
"terms": {
"field": "job.keyword"
}
},
"max_salary":{
"max": {
"field": "salary"
}
}
}
}
# msearch 拆分多个语句的聚合实现
GET _msearch
{"index":"employees"}
{"size":0,"aggs":{"job_agg":{"terms":{"field": "job.keyword"}}}}
{"index":"employees"}
{"size":0,"aggs":{"max_salary":{"max":{"field": "salary"}}}}