【Elasticsearch】 Composite Aggregation 详解
1.什么是 Composite Aggregation?
Composite Aggregation 是 Elasticsearch 中的一种特殊聚合方式,适用于需要分页展示的聚合结果。它与传统的聚合方式不同,采用了基于游标的分页模型。这种聚合方式可以高效地处理多级聚合中的所有桶,并支持分页功能。
2.基本结构
一个典型的 Composite Aggregation 查询如下:
```json
GET /your_index_name/_search
{
\"size\": 0,
\"aggs\": {
\"my_composite_agg\": {
\"composite\": {
\"size\": 10,
\"sources\": [
{
\"field1\": {
\"terms\": {
\"field\": \"your_field_name1\"
}
}
},
{
\"field2\": {
\"terms\": {
\"field\": \"your_field_name2\"
}
}
}
]
}
}
}
}
```
在上述查询中:
• `sources`定义了按哪些字段分组,字段顺序决定了分组键(bucket key)的生成顺序。
• `size`定义每页的桶数量。
• 响应结果中的`after_key`用于获取下一页数据。
3.分页机制
Composite Aggregation 的分页机制通过`after`参数实现。每次查询返回指定数量的桶,并通过`after_key`提供下一页的游标。这种方式可以确保分页查询中数据无重复、无遗漏。
例如:
```json
GET /your_index_name/_search
{
\"size\": 0,
\"aggs\": {
\"my_composite_agg\": {
\"composite\": {
\"size\": 10,
\"sources\": [
{
\"field1\": {
\"terms\": {
\"field\": \"your_field_name1\"
}
}
}
],
\"after\": {
\"field1\": \"last_value_of_field1\"
}
}
}
}
}
``
4.排序和方向
Composite Aggregation 支持对每个值源进行排序,可以通过设置`order`参数为`asc`(升序)或`desc`(降序)。
```json
GET /your_index_name/_search
{
\"size\": 0,
\"aggs\": {
\"my_composite_agg\": {
\"composite\": {
\"size\": 10,
\"sources\": [
{
\"field1\": {
\"terms\": {
\"field\": \"your_field_name1\",
\"order\": \"desc\"
}
}
},
{
\"field2\": {
\"terms\": {
\"field\": \"your_field_name2\",
\"order\": \"asc\"
}
}
}
]
}
}
}
}
```
5.处理缺失值
默认情况下,缺少指定字段值的文档会被忽略。通过设置`missing_bucket`参数为`true`,可以将这些文档包含在响应中。
```json
GET /your_index_name/_search
{
\"size\": 0,
\"aggs\": {
\"my_composite_agg\": {
\"composite\": {
\"size\": 10,
\"sources\": [
{
\"field1\": {
\"terms\": {
\"field\": \"your_field_name1\",
\"missing_bucket\": true
}
}
}
]
}
}
}
}
```
6.性能优化
Composite Aggregation 的设计特别适合大规模数据的聚合和分页,是传统`from + size`分页方法的高效替代方案。为了进一步优化性能,建议在索引中设置索引排序,使其与复合聚合中的源顺序部分或完全匹配。
7.应用场景
Composite Aggregation 适用于以下场景:
• 需要分页展示聚合结果。
• 处理大规模数据时,需要高效分页和排序。
• 需要对多个字段进行分组和聚合。
通过上述特性,Composite Aggregation 提供了一种强大且灵活的方式来处理复杂的聚合需求,特别是在需要分页和排序的场景中表现出色。
好的,下面我将通过一个具体的例子来展示如何使用 Composite Aggregation 来实现分页聚合查询。假设我们有一个电商数据集,其中包含商品的销售记录,我们希望按日期和商品类别进行分组,并计算每个分组的销售总额。
数据示例
假设我们的索引名为`sales`,其中的文档如下:
```json
{
\"timestamp\": \"2024-01-01T00:00:00Z\",
\"product\": \"T-shirt\",
\"category\": \"Clothing\",
\"price\": 20
}
{
\"timestamp\": \"2024-01-01T00:00:00Z\",
\"product\": \"Jeans\",
\"category\": \"Clothing\",
\"price\": 40
}
{
\"timestamp\": \"2024-01-02T00:00:00Z\",
\"product\": \"T-shirt\",
\"category\": \"Clothing\",
\"price\": 20
}
{
\"timestamp\": \"2024-01-02T00:00:00Z\",
\"product\": \"Laptop\",
\"category\": \"Electronics\",
\"price\": 1000
}
```
查询目标
我们希望按日期和商品类别进行分组,并计算每个分组的销售总额。同时,我们希望分页显示结果,每页显示 2 个分组。
第一页查询
首先,我们查询第一页的结果:
```json
GET /sales/_search
{
\"size\": 0,
\"aggs\": {
\"sales_by_date_and_category\": {
\"composite\": {
\"size\": 2,
\"sources\": [
{
\"date\": {
\"date_histogram\": {
\"field\": \"timestamp\",
\"calendar_interval\": \"1d\"
}
}
},
{
\"category\": {
\"terms\": {
\"field\": \"category\"
}
}
}
]
},
\"aggregations\": {
\"total_sales\": {
\"sum\": {
\"field\": \"price\"
}
}
}
}
}
}
```
解释
• `size`:每页返回的分组数量。
• `sources`:定义了两个分组字段:
• `date`:按日期分组,每天一个桶。
• `category`:按商品类别分组。
• `aggregations`:在每个复合桶中计算销售总额。
查询结果
返回的结果如下:
```json
{
\"aggregations\": {
\"sales_by_date_and_category\": {
\"after_key\": {
\"date\": 1704115200000,
\"category\": \"Clothing\"
},
\"buckets\": [
{
\"key\": {
\"date\": 1704028800000,
\"category\": \"Clothing\"
},
\"doc_count\": 2,
\"total_sales\": {
\"value\": 60
}
},
{
\"key\": {
\"date\": 1704115200000,
\"category\": \"Clothing\"
},
\"doc_count\": 1,
\"total_sales\": {
\"value\": 20
}
}
]
}
}
}
```
解释
• `buckets`:包含两个分组:
• 第一个分组:`2024-01-01`的`Clothing`类别,销售总额为 60。
• 第二个分组:`2024-01-02`的`Clothing`类别,销售总额为 20。
• `after_key`:提供了下一页的游标。
第二页查询
使用`after_key`查询下一页的结果:
```json
GET /sales/_search
{
\"size\": 0,
\"aggs\": {
\"sales_by_date_and_category\": {
\"composite\": {
\"size\": 2,
\"sources\": [
{
\"date\": {
\"date_histogram\": {
\"field\": \"timestamp\",
\"calendar_interval\": \"1d\"
}
}
},
{
\"category\": {
\"terms\": {
\"field\": \"category\"
}
}
}
],
\"after\": {
\"date\": 1704115200000,
\"category\": \"Clothing\"
}
},
\"aggregations\": {
\"total_sales\": {
\"sum\": {
\"field\": \"price\"
}
}
}
}
}
}
``
查询结果
返回的结果如下:
```json
{
\"aggregations\": {
\"sales_by_date_and_category\": {
\"after_key\": {
\"date\": 1704115200000,
\"category\": \"Electronics\"
},
\"buckets\": [
{
\"key\": {
\"date\": 1704115200000,
\"category\": \"Electronics\"
},
\"doc_count\": 1,
\"total_sales\": {
\"value\": 1000
}
}
]
}
}
}
``
解释
• `buckets`:包含一个分组:
• `2024-01-02`的`Electronics`类别,销售总额为 1000。
• `after_key`:提供了下一页的游标。
通过这种方式,我们可以高效地分页查询聚合结果,而不会遗漏或重复任何数据。
好的,接下来我们继续探讨如何处理更多分页结果,以及如何优化和扩展这个查询。
继续分页查询
假设我们继续查询下一页,使用上一页返回的`after_key`:
```json
GET /sales/_search
{
\"size\": 0,
\"aggs\": {
\"sales_by_date_and_category\": {
\"composite\": {
\"size\": 2,
\"sources\": [
{
\"date\": {
\"date_histogram\": {
\"field\": \"timestamp\",
\"calendar_interval\": \"1d\"
}
}
},
{
\"category\": {
\"terms\": {
\"field\": \"category\"
}
}
}
],
\"after\": {
\"date\": 1704115200000,
\"category\": \"Electronics\"
}
},
\"aggregations\": {
\"total_sales\": {
\"sum\": {
\"field\": \"price\"
}
}
}
}
}
}
```
查询结果
如果返回结果为空,说明已经到达最后一页:
```json
{
\"aggregations\": {
\"sales_by_date_and_category\": {
\"buckets\": []
}
}
}
``
这表明所有分组已经查询完毕。
性能优化
为了进一步优化性能,可以考虑以下几点:
1. 设置合理的`size`参数:根据实际需求设置合适的分页大小,避免过大或过小。
2. 索引排序:如果数据量很大,可以在索引创建时设置索引排序,使其与聚合的字段顺序一致。例如:
```json
PUT /sales
{
\"settings\": {
\"index\": {
\"sort.field\": [\"timestamp\", \"category\"],
\"sort.order\": [\"asc\", \"asc\"]
}
}
}
```
3. 禁用`track_total_hits`:在分页查询中,通常不需要统计总命中数,可以通过设置`track_total_hits: false`来节省资源。
扩展应用
Composite Aggregation 不仅可以用于分页查询,还可以结合其他聚合功能,例如:
• 计算平均值:在每个分组中计算平均销售额。
```json
\"aggregations\": {
\"average_sales\": {
\"avg\": {
\"field\": \"price\"
}
}
}
```
• 多级分组:可以增加更多分组字段,例如按地区分组。
```json
\"sources\": [
{
\"date\": {
\"date_histogram\": {
\"field\": \"timestamp\",
\"calendar_interval\": \"1d\"
}
}
},
{
\"category\": {
\"terms\": {
\"field\": \"category\"
}
}
},
{
\"region\": {
\"terms\": {
\"field\": \"region\"
}
}
}
]
```
总结
通过 Composite Aggregation,我们可以高效地实现分页聚合查询,避免了传统分页方法(如`from + size`)在大规模数据下的性能瓶颈。同时,它还支持灵活的排序、多级分组和子聚合功能,能够满足复杂的业务需求。
以下是使用`composite aggregation`结合`terms`、`histogram`、`date_histogram`和`geotile_grid`的示例:
1.`terms`类型的`composite aggregation`
以下示例对`authors`索引中的`author_name`字段进行`terms`聚合:
```json
GET authors/_search
{
\"size\": 0,
\"aggs\": {
\"our_buckets\": {
\"composite\": {
\"sources\": [
{ \"authors\": { \"terms\": { \"field\": \"author_name\" } } }
]
}
}
}
}
```
2.`histogram`类型的`composite aggregation`
以下示例对`authors`索引中的`books_number`字段进行`histogram`聚合,区间设置为5:
```json
GET authors/_search
{
\"size\": 0,
\"aggs\": {
\"our_buckets\": {
\"composite\": {
\"sources\": [
{ \"booksnum\": { \"histogram\": { \"field\": \"books_number\", \"interval\": 5 } } }
]
}
}
}
}
```
3.`date_histogram`类型的`composite aggregation`
以下示例对`books`索引中的`publish_date`字段进行`date_histogram`聚合,时间间隔设置为一周:
```json
GET books/_search
{
\"size\": 0,
\"aggs\": {
\"our_buckets\": {
\"composite\": {
\"sources\": [
{ \"week\": { \"date_histogram\": { \"field\": \"publish_date\", \"calendar_interval\": \"1w\" } } }
]
}
}
}
}
```
4.`geotile_grid`类型的`composite aggregation`
以下示例对`authors`索引中的`authors_location`字段进行`geotile_grid`聚合,精度设置为6:
```json
GET authors/_search
{
\"size\": 0,
\"aggs\": {
\"our_buckets\": {
\"composite\": {
\"sources\": [
{ \"authorsloc\": { \"geotile_grid\": { \"field\": \"authors_location\", \"precision\": 6 } } }
]
}
}
}
}
```
5.组合使用多种聚合类型
以下示例同时使用`date_histogram`和`terms`聚合:
```json
GET books/_search
{
\"size\": 0,
\"aggs\": {
\"our_buckets\": {
\"composite\": {
\"sources\": [
{ \"week\": { \"date_histogram\": { \"field\": \"publish_date\", \"calendar_interval\": \"1w\" } } },
{ \"authors\": { \"terms\": { \"field\": \"author_name\" } } }
]
}
}
}
}
```
这些示例展示了如何在`composite aggregation`中使用不同类型的聚合,以满足不同的数据分析需求。