复合聚合
编辑复合聚合编辑
复合聚合的开销很大。在生产环境中部署复合聚合之前,请先对您的应用程序进行负载测试。
一种多桶聚合,可以从不同的来源创建复合桶。
与其他 多桶
聚合不同,您可以使用 复合
聚合有效地对多级聚合中的 所有 桶进行分页。此聚合提供了一种流式传输特定聚合的 所有 桶的方法,类似于 滚动 对文档的作用。
复合桶是根据为每个文档提取/创建的值的组合构建的,每个组合都被视为一个复合桶。
例如,请考虑以下文档
{ "keyword": ["foo", "bar"], "number": [23, 65, 76] }
使用 keyword
和 number
作为聚合的源字段会产生以下复合桶
{ "keyword": "foo", "number": 23 } { "keyword": "foo", "number": 65 } { "keyword": "foo", "number": 76 } { "keyword": "bar", "number": 23 } { "keyword": "bar", "number": 65 } { "keyword": "bar", "number": 76 }
值源编辑
sources
参数定义了在构建复合桶时要使用的源字段。定义 sources
的顺序控制着返回键的顺序。
定义 sources
时必须使用唯一的名称。
sources
参数可以是以下任何类型
词条编辑
terms
值源类似于简单的 terms
聚合。值的提取方式与 terms
聚合完全相同。
示例
response = client.search( body: { size: 0, aggregations: { my_buckets: { composite: { sources: [ { product: { terms: { field: 'product' } } } ] } } } } ) puts response
GET /_search { "size": 0, "aggs": { "my_buckets": { "composite": { "sources": [ { "product": { "terms": { "field": "product" } } } ] } } } }
与 terms
聚合一样,可以使用 运行时字段 为复合桶创建值
response = client.search( body: { runtime_mappings: { day_of_week: { type: 'keyword', script: "\n emit(doc['timestamp'].value.dayOfWeekEnum\n .getDisplayName(TextStyle.FULL, Locale.ROOT))\n " } }, size: 0, aggregations: { my_buckets: { composite: { sources: [ { dow: { terms: { field: 'day_of_week' } } } ] } } } } ) puts response
GET /_search { "runtime_mappings": { "day_of_week": { "type": "keyword", "script": """ emit(doc['timestamp'].value.dayOfWeekEnum .getDisplayName(TextStyle.FULL, Locale.ROOT)) """ } }, "size": 0, "aggs": { "my_buckets": { "composite": { "sources": [ { "dow": { "terms": { "field": "day_of_week" } } } ] } } } }
虽然类似,但 terms
值源不支持与 terms
聚合相同的参数集。有关其他受支持的值源参数,请参阅
直方图编辑
histogram
值源可以应用于数值,以在值上构建固定大小的间隔。interval
参数定义了如何转换数值。例如,将 interval
设置为 5 会将任何数值转换为其最接近的间隔,值 101
将转换为 100
,这是 100 到 105 之间间隔的键。
示例
response = client.search( body: { size: 0, aggregations: { my_buckets: { composite: { sources: [ { histo: { histogram: { field: 'price', interval: 5 } } } ] } } } } ) puts response
GET /_search { "size": 0, "aggs": { "my_buckets": { "composite": { "sources": [ { "histo": { "histogram": { "field": "price", "interval": 5 } } } ] } } } }
与 histogram
聚合一样,可以使用 运行时字段 为复合桶创建值
response = client.search( body: { runtime_mappings: { 'price.discounted' => { type: 'double', script: "\n double price = doc['price'].value;\n if (doc['product'].value == 'mad max') {\n price *= 0.8;\n }\n emit(price);\n " } }, size: 0, aggregations: { my_buckets: { composite: { sources: [ { price: { histogram: { interval: 5, field: 'price.discounted' } } } ] } } } } ) puts response
GET /_search { "runtime_mappings": { "price.discounted": { "type": "double", "script": """ double price = doc['price'].value; if (doc['product'].value == 'mad max') { price *= 0.8; } emit(price); """ } }, "size": 0, "aggs": { "my_buckets": { "composite": { "sources": [ { "price": { "histogram": { "interval": 5, "field": "price.discounted" } } } ] } } } }
日期直方图编辑
date_histogram
与 histogram
值源类似,只是间隔由日期/时间表达式指定
response = client.search( body: { size: 0, aggregations: { my_buckets: { composite: { sources: [ { date: { date_histogram: { field: 'timestamp', calendar_interval: '1d' } } } ] } } } } ) puts response
GET /_search { "size": 0, "aggs": { "my_buckets": { "composite": { "sources": [ { "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d" } } } ] } } } }
上面的示例每天创建一个间隔,并将所有 timestamp
值转换为其最接近间隔的开始时间。间隔的可用表达式:year
、quarter
、month
、week
、day
、hour
、minute
、second
时间值也可以通过 时间单位 解析支持的缩写来指定。请注意,不支持小数时间值,但您可以通过转换为另一个时间单位来解决此问题(例如,1.5h
可以指定为 90m
)。
格式
在内部,日期表示为一个 64 位数字,表示自纪元以来的毫秒数时间戳。这些时间戳作为桶键返回。可以使用 format 参数指定的格式返回格式化的日期字符串
response = client.search( body: { size: 0, aggregations: { my_buckets: { composite: { sources: [ { date: { date_histogram: { field: 'timestamp', calendar_interval: '1d', format: 'yyyy-MM-dd' } } } ] } } } } ) puts response
GET /_search { "size": 0, "aggs": { "my_buckets": { "composite": { "sources": [ { "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d", "format": "yyyy-MM-dd" } } } ] } } } }
支持表达式的日期 格式模式 |
时区
日期时间在 Elasticsearch 中以 UTC 格式存储。默认情况下,所有分桶和舍入操作也以 UTC 格式完成。time_zone
参数可用于指示分桶应使用不同的时区。
时区可以指定为 ISO 8601 UTC 偏移量(例如 +01:00
或 -08:00
)或时区 ID,即 TZ 数据库中使用的标识符,如 America/Los_Angeles
。
偏移量
使用 offset
参数,可以通过指定的正 (+
) 或负偏移量 (-
) 持续时间来更改每个桶的起始值,例如 1h
表示一小时,或 1d
表示一天。有关更多可能的时间持续时间选项,请参阅 时间单位。
例如,当使用 day
的间隔时,每个桶的运行时间是从午夜到午夜。将 offset
参数设置为 +6h
会将每个桶的运行时间更改为从早上 6 点到早上 6 点
response = client.index( index: 'my-index-000001', id: 1, refresh: true, body: { date: '2015-10-01T05:30:00Z' } ) puts response response = client.index( index: 'my-index-000001', id: 2, refresh: true, body: { date: '2015-10-01T06:30:00Z' } ) puts response response = client.search( index: 'my-index-000001', size: 0, body: { aggregations: { my_buckets: { composite: { sources: [ { date: { date_histogram: { field: 'date', calendar_interval: 'day', offset: '+6h', format: 'iso8601' } } } ] } } } } ) puts response
PUT my-index-000001/_doc/1?refresh { "date": "2015-10-01T05:30:00Z" } PUT my-index-000001/_doc/2?refresh { "date": "2015-10-01T06:30:00Z" } GET my-index-000001/_search?size=0 { "aggs": { "my_buckets": { "composite" : { "sources" : [ { "date": { "date_histogram" : { "field": "date", "calendar_interval": "day", "offset": "+6h", "format": "iso8601" } } } ] } } } }
上面的请求没有将一个桶的起始时间设置为午夜,而是将文档分组到从早上 6 点开始的桶中
{ ... "aggregations": { "my_buckets": { "after_key": { "date": "2015-10-01T06:00:00.000Z" }, "buckets": [ { "key": { "date": "2015-09-30T06:00:00.000Z" }, "doc_count": 1 }, { "key": { "date": "2015-10-01T06:00:00.000Z" }, "doc_count": 1 } ] } } }
每个桶的起始 offset
是在进行 time_zone
调整后计算的。
地理图块网格编辑
geotile_grid
值源作用于 geo_point
字段,并将点分组到表示网格中单元格的桶中。生成的网格可以是稀疏的,并且只包含具有匹配数据的单元格。每个单元格对应于许多在线地图网站使用的 地图图块。每个单元格都使用“{zoom}/{x}/{y}”格式标记,其中 zoom 等于用户指定的精度。
response = client.search( body: { size: 0, aggregations: { my_buckets: { composite: { sources: [ { tile: { geotile_grid: { field: 'location', precision: 8 } } } ] } } } } ) puts response
GET /_search { "size": 0, "aggs": { "my_buckets": { "composite": { "sources": [ { "tile": { "geotile_grid": { "field": "location", "precision": 8 } } } ] } } } }
精度
长度为 29 的最高精度地理图块生成的单元格覆盖的陆地面积不到 10 厘米 x 10 厘米。这种精度特别适用于复合聚合,因为每个图块不必在内存中生成和加载。
有关精度(缩放级别)如何与地面大小相关联,请参阅 缩放级别文档。此聚合的精度可以在 0 到 29 之间(含)。
边界框过滤
可以选择将地理图块源限制为特定的地理边界框,这会减少使用的图块范围。当只需要地理区域的特定部分进行高精度平铺时,这些边界非常有用。
response = client.search( body: { size: 0, aggregations: { my_buckets: { composite: { sources: [ { tile: { geotile_grid: { field: 'location', precision: 22, bounds: { top_left: 'POINT (4.9 52.4)', bottom_right: 'POINT (5.0 52.3)' } } } } ] } } } } ) puts response
GET /_search { "size": 0, "aggs": { "my_buckets": { "composite": { "sources": [ { "tile": { "geotile_grid": { "field": "location", "precision": 22, "bounds": { "top_left": "POINT (4.9 52.4)", "bottom_right": "POINT (5.0 52.3)" } } } } ] } } } }
混合不同的值源编辑
sources
参数接受一个值源数组。可以混合不同的值源来创建复合桶。例如
response = client.search( body: { size: 0, aggregations: { my_buckets: { composite: { sources: [ { date: { date_histogram: { field: 'timestamp', calendar_interval: '1d' } } }, { product: { terms: { field: 'product' } } } ] } } } } ) puts response
GET /_search { "size": 0, "aggs": { "my_buckets": { "composite": { "sources": [ { "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d" } } }, { "product": { "terms": { "field": "product" } } } ] } } } }
这将根据两个值源(一个 date_histogram
和一个 terms
)创建的值创建复合桶。每个桶都由两个值组成,每个值对应于聚合中定义的一个值源。允许任何类型的组合,并且数组中的顺序在复合桶中保留。
response = client.search( body: { size: 0, aggregations: { my_buckets: { composite: { sources: [ { shop: { terms: { field: 'shop' } } }, { product: { terms: { field: 'product' } } }, { date: { date_histogram: { field: 'timestamp', calendar_interval: '1d' } } } ] } } } } ) puts response
GET /_search { "size": 0, "aggs": { "my_buckets": { "composite": { "sources": [ { "shop": { "terms": { "field": "shop" } } }, { "product": { "terms": { "field": "product" } } }, { "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d" } } } ] } } } }
顺序编辑
默认情况下,复合桶按其自然顺序排序。值按其值的升序排序。当请求多个值源时,排序是按值源进行的,将复合桶的第一个值与另一个复合桶的第一个值进行比较,如果它们相等,则使用复合桶中的下一个值进行平局决胜。这意味着复合桶 [foo, 100]
被认为小于 [foobar, 0]
,因为 foo
被认为小于 foobar
。可以通过直接在值源定义中将 order
设置为 asc
(默认值)或 desc
(降序)来定义每个值源的排序方向。例如
response = client.search( body: { size: 0, aggregations: { my_buckets: { composite: { sources: [ { date: { date_histogram: { field: 'timestamp', calendar_interval: '1d', order: 'desc' } } }, { product: { terms: { field: 'product', order: 'asc' } } } ] } } } } ) puts response
GET /_search { "size": 0, "aggs": { "my_buckets": { "composite": { "sources": [ { "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d", "order": "desc" } } }, { "product": { "terms": { "field": "product", "order": "asc" } } } ] } } } }
... 在比较来自 date_histogram
源的值时,将按降序对复合桶进行排序;在比较来自 terms
源的值时,将按升序对复合桶进行排序。
缺失桶编辑
默认情况下,将忽略给定源没有值的文档。可以通过将 missing_bucket
设置为 true
(默认为 false
)将其包含在响应中
response = client.search( body: { size: 0, aggregations: { my_buckets: { composite: { sources: [ { product_name: { terms: { field: 'product', missing_bucket: true, missing_order: 'last' } } } ] } } } } ) puts response
GET /_search { "size": 0, "aggs": { "my_buckets": { "composite": { "sources": [{ "product_name": { "terms": { "field": "product", "missing_bucket": true, "missing_order": "last" } } }] } } } }
在上面的示例中,对于没有 product
值的文档,product_name
源会发出一个显式的 null
桶。此桶位于最后。
您可以使用可选的 missing_order
参数控制 null
桶的位置。如果 missing_order
是 first
或 last
,则 null
桶分别位于第一个或最后一个位置。如果省略 missing_order
或将其设置为 default
,则源的 order
决定桶的位置。如果 order
是 asc
(升序),则桶位于第一个位置。如果 order
是 desc
(降序),则桶位于最后一个位置。
大小编辑
可以使用 size
参数来定义应该返回多少个组合桶。每个组合桶都被视为一个桶,因此将大小设置为 10 将返回从值源创建的前 10 个组合桶。响应在一个数组中包含每个组合桶的值,该数组包含从每个值源提取的值。默认为 10
。
分页编辑
如果组合桶的数量太多(或未知)而无法在单个响应中返回,则可以将检索拆分为多个请求。由于组合桶本质上是扁平的,因此请求的 size
正好是响应中返回的组合桶的数量(假设它们至少有 size
个组合桶要返回)。如果要检索所有组合桶,最好使用较小的 size(例如 100
或 1000
),然后使用 after
参数检索下一个结果。例如
response = client.search( body: { size: 0, aggregations: { my_buckets: { composite: { size: 2, sources: [ { date: { date_histogram: { field: 'timestamp', calendar_interval: '1d' } } }, { product: { terms: { field: 'product' } } } ] } } } } ) puts response
GET /_search { "size": 0, "aggs": { "my_buckets": { "composite": { "size": 2, "sources": [ { "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d" } } }, { "product": { "terms": { "field": "product" } } } ] } } } }
... 返回
{ ... "aggregations": { "my_buckets": { "after_key": { "date": 1494288000000, "product": "mad max" }, "buckets": [ { "key": { "date": 1494201600000, "product": "rocky" }, "doc_count": 1 }, { "key": { "date": 1494288000000, "product": "mad max" }, "doc_count": 2 } ] } } }
要获取下一组桶,请使用设置为响应中返回的 after_key
值的 after
参数重新发送相同的聚合。例如,此请求使用上一个响应中提供的 after_key
值
response = client.search( body: { size: 0, aggregations: { my_buckets: { composite: { size: 2, sources: [ { date: { date_histogram: { field: 'timestamp', calendar_interval: '1d', order: 'desc' } } }, { product: { terms: { field: 'product', order: 'asc' } } } ], after: { date: 1_494_288_000_000, product: 'mad max' } } } } } ) puts response
GET /_search { "size": 0, "aggs": { "my_buckets": { "composite": { "size": 2, "sources": [ { "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d", "order": "desc" } } }, { "product": { "terms": { "field": "product", "order": "asc" } } } ], "after": { "date": 1494288000000, "product": "mad max" } } } } }
after_key
通常是响应中返回的最后一个桶的键,但这不能保证。始终使用返回的 after_key
,而不是从桶中派生它。
提前终止编辑
为了获得最佳性能,应该在索引上设置 索引排序,使其部分或完全匹配组合聚合中的源顺序。例如,以下索引排序
response = client.indices.create( index: 'my-index-000001', body: { settings: { index: { 'sort.field' => [ 'username', 'timestamp' ], 'sort.order' => [ 'asc', 'desc' ] } }, mappings: { properties: { username: { type: 'keyword', doc_values: true }, timestamp: { type: 'date' } } } } ) puts response
PUT my-index-000001 { "settings": { "index": { "sort.field": [ "username", "timestamp" ], "sort.order": [ "asc", "desc" ] } }, "mappings": { "properties": { "username": { "type": "keyword", "doc_values": true }, "timestamp": { "type": "date" } } } }
response = client.search( body: { size: 0, aggregations: { my_buckets: { composite: { sources: [ { user_name: { terms: { field: 'user_name' } } } ] } } } } ) puts response
GET /_search { "size": 0, "aggs": { "my_buckets": { "composite": { "sources": [ { "user_name": { "terms": { "field": "user_name" } } } ] } } } }
response = client.search( body: { size: 0, aggregations: { my_buckets: { composite: { sources: [ { user_name: { terms: { field: 'user_name' } } }, { date: { date_histogram: { field: 'timestamp', calendar_interval: '1d', order: 'desc' } } } ] } } } } ) puts response
GET /_search { "size": 0, "aggs": { "my_buckets": { "composite": { "sources": [ { "user_name": { "terms": { "field": "user_name" } } }, { "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d", "order": "desc" } } } ] } } } }
为了优化提前终止,建议在请求中将 track_total_hits
设置为 false
。可以在第一个请求中检索与请求匹配的总命中数,并且在每个页面上计算此数字的成本很高
response = client.search( body: { size: 0, track_total_hits: false, aggregations: { my_buckets: { composite: { sources: [ { user_name: { terms: { field: 'user_name' } } }, { date: { date_histogram: { field: 'timestamp', calendar_interval: '1d', order: 'desc' } } } ] } } } } ) puts response
GET /_search { "size": 0, "track_total_hits": false, "aggs": { "my_buckets": { "composite": { "sources": [ { "user_name": { "terms": { "field": "user_name" } } }, { "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d", "order": "desc" } } } ] } } } }
请注意,源的顺序很重要,在下面的示例中,将 user_name
与 timestamp
交换将停用排序优化,因为此配置与索引排序规范不匹配。如果源的顺序对您的用例无关紧要,您可以遵循以下简单指南
- 将基数最高的字段放在首位。
- 确保字段的顺序与索引排序的顺序匹配。
- 将多值字段放在最后,因为它们不能用于提前终止。
索引排序 会降低索引速度,使用您的特定用例和数据集测试索引排序以确保它符合您的要求非常重要。如果没有,请注意,如果查询匹配所有文档(match_all
查询),composite
聚合也会尝试在未排序的索引上提前终止。
子聚合编辑
与任何 multi-bucket
聚合一样,composite
聚合可以包含子聚合。这些子聚合可用于计算此父聚合创建的每个组合桶上的其他桶或统计信息。例如,以下示例计算每个组合桶的字段的平均值
response = client.search( body: { size: 0, aggregations: { my_buckets: { composite: { sources: [ { date: { date_histogram: { field: 'timestamp', calendar_interval: '1d', order: 'desc' } } }, { product: { terms: { field: 'product' } } } ] }, aggregations: { the_avg: { avg: { field: 'price' } } } } } } ) puts response
GET /_search { "size": 0, "aggs": { "my_buckets": { "composite": { "sources": [ { "date": { "date_histogram": { "field": "timestamp", "calendar_interval": "1d", "order": "desc" } } }, { "product": { "terms": { "field": "product" } } } ] }, "aggregations": { "the_avg": { "avg": { "field": "price" } } } } } }
... 返回
{ ... "aggregations": { "my_buckets": { "after_key": { "date": 1494201600000, "product": "rocky" }, "buckets": [ { "key": { "date": 1494460800000, "product": "apocalypse now" }, "doc_count": 1, "the_avg": { "value": 10.0 } }, { "key": { "date": 1494374400000, "product": "mad max" }, "doc_count": 1, "the_avg": { "value": 27.0 } }, { "key": { "date": 1494288000000, "product": "mad max" }, "doc_count": 2, "the_avg": { "value": 22.5 } }, { "key": { "date": 1494201600000, "product": "rocky" }, "doc_count": 1, "the_avg": { "value": 10.0 } } ] } } }
管道聚合编辑
组合聚合当前不兼容管道聚合,并且在大多数情况下也没有意义。例如,由于组合聚合的分页性质,单个逻辑分区(例如一天)可能会分布在多个页面上。由于管道聚合纯粹是对最终桶列表进行后处理,因此在组合页面上运行类似导数的操作可能会导致结果不准确,因为它只考虑了该页面上的“部分”结果。
将来可能会支持自包含在单个桶中的管道聚合(例如 bucket_selector
)。