聚合框架帮助提供基于搜索查询的聚合数据。它基于称为聚合(aggregations)的简单构建块,可以对其进行组合以构建复杂的数据摘要。
聚合可以看作是在一组文档上构建分析信息的工作单元。执行的上下文定义了该文档集是什么(例如,在搜索请求的已执行查询/筛选器的上下文中执行顶级聚合)。
聚合有许多不同的类型,每种类型都有自己的目的和输出。为了更好地理解这些类型,通常更容易将它们分为四个主要系列:
生成存储桶的聚合系列,其中每个存储桶都与一个键和一个文档条件相关联。执行聚合时,将对上下文中的每个文档计算所有bucket条件,当某个条件匹配时,将认为该文档属于相关bucket。在聚合过程结束时,我们将得到一个bucket列表——每个bucket都有一组“属于”它的文档。
在一组文档上跟踪和计算度量的聚合。
对多个字段进行操作并基于从请求的文档字段中提取的值生成矩阵结果的聚合系列。与度量和bucket聚合不同,此聚合系列尚不支持脚本。
聚合其他聚合的输出及其相关度量的聚合。
接下来是有趣的部分。由于每个bucket有效地定义了一个文档集(属于bucket的所有文档),因此可以在bucket级别上潜在地关联聚合,这些聚合将在该bucket的上下文中执行。这就是聚合的真正威力所在:聚合可以嵌套!
注意:
存储桶聚合可以具有子聚合(存储桶或指标)。子聚合将针对其父聚合生成的存储桶进行计算。嵌套聚合的级别/深度没有硬性限制(可以将一个聚合嵌套在“父”聚合下,该“父”聚合本身是另一种更高级别的聚合的子聚合)。
注意:
聚合作用于
double
数据的表示形式。因此,当运行绝对值大于的多头时,结果可能是近似的2^53
。
构建聚合
以下代码段捕获了聚合的基本结构:
"aggregations" : {
"<aggregation_name>" : {
"<aggregation_type>" : {
<aggregation_body>
}
[,"meta" : { [<meta_data_body>] } ]?
[,"aggregations" : { [<sub_aggregation>]+ } ]?
}
[,"<aggregation_name_2>" : { ... } ]*
}
JSON中的aggregations
对象(也可以使用键aggs
)保存要计算的聚合。每个聚合都与用户定义的逻辑名称相关联(例如,如果聚合计算平均价格,则将其命名avg_price
是有意义的)。这些逻辑名称还将用于唯一地标识响应中的聚合。每个聚合都有特定的类型(<aggregation_type>
在上面的代码段中),通常是命名聚合主体中的第一个键。每种聚合类型都定义自己的主体,具体取决于聚合的性质(例如,特定字段上的avg聚合
将定义将在其上计算平均值的字段)。在聚合类型定义的同一级别上,可以选择定义一组其他聚合,尽管仅当您定义的聚合具有存储特性时才有意义。在这种情况下,将为存储桶聚合级别构建的所有存储桶计算您在存储桶聚合级别上定义的子聚合。例如,如果您在聚合下定义了一组range
聚合,则将为定义的范围存储桶计算子聚合。
值来源
一些聚合处理从聚合文档中提取的值。通常,这些值将从使用聚合字段键设置的特定文档字段中提取。还可以定义一个脚本来生成值(每个文档)。
为聚合配置字段和脚本设置时,脚本将被视为值脚本。正常脚本在文档级别进行评估(即脚本可以访问与文档相关的所有数据),而值脚本在值级别进行评估。在这种模式下,将从配置的字段中提取值,并使用脚本对这些值/秒应用“转换”。
一些汇总处理从汇总文档中提取的值。通常,将从将使用field
聚合键设置的特定文档字段中提取值。也可以定义一个 script
将生成值的文档(每个文档)。
为聚合配置了field
和script
设置时,脚本将被视为 value script
。在文档级别评估普通脚本(即脚本可以访问与文档关联的所有数据)时,价值脚本在价值级别评估。在这种模式下,从配置中提取值,并使用field
对这些script
值进行“转换”。
使用脚本时,还可以定义
lang
和params
设置。前者定义了使用的脚本语言(假设默认情况下或作为插件,Elasticsearch中可以使用正确的语言)。后者允许将脚本中的所有“动态”表达式定义为参数,从而使脚本在调用之间保持自身静态(这将确保在Elasticsearch中使用缓存的已编译脚本)。
Elasticsearch使用映射中字段的类型,以确定如何运行聚合并格式化响应。但是,Elasticsearch无法找出这一信息的情况有两种:未映射字段(例如,在跨多个索引的搜索请求中,只有一些字段具有字段映射)和纯脚本。对于这些情况,可以使用value_type选项向Elasticsearch提供提示,该选项接受以下值:string、long(适用于所有整数类型)、double(适用于所有十进制类型,如float或scaled_float)、date、ip和boolean。
(Metrics)度量聚合
此系列中的聚合基于以某种方式从正在聚合的文档中提取的值计算度量。这些值通常从文档的字段中提取(使用字段数据),但也可以使用脚本生成。
数值度量聚合是一种特殊的度量聚合类型,它输出数值。一些聚合输出单个数值度量(例如avg),称为单值数值度量聚合,其他聚合生成多个度量(例如stats),称为多值数值度量聚合。当这些聚合充当某些bucket聚合的直接子聚合(某些bucket聚合使您能够根据每个bucket中的数字度量对返回的bucket进行排序)时,单值和多值数值度量聚合之间的区别发挥了作用。
平均聚合
一种单值度量聚合,用于计算从聚合文档中提取的数值的平均值。这些值可以从文档中的特定数字字段中提取,也可以由提供的脚本生成。
假设数据由代表学生考试成绩(0到100之间)的文件组成,我们可以通过以下方法对他们的分数进行平均:
POST /exams/_search?size=0
{
"aggs" : {
"avg_grade" : { "avg" : { "field" : "grade" } }
}
}
上面的聚合计算所有文档的平均级别。聚合类型是avg,字段设置定义要计算平均值的文档的数字字段。以上将返回以下内容:
{
...
"aggregations": {
"avg_grade": {
"value": 75.0
}
}
}
聚合的名称(上面的avg_grade)也用作从返回的响应中检索聚合结果的密钥。
(script)脚本
基于script计算平均成绩:
POST /exams/_search?size=0
{
"aggs" : {
"avg_grade" : {
"avg" : {
"script" : {
"source" : "doc.grade.value"
}
}
}
}
}
这会将script
参数解释为inline
具有painless
脚本语言且没有脚本参数的脚本。要使用存储的脚本,请使用以下语法:
POST /exams/_search?size=0
{
"aggs" : {
"avg_grade" : {
"avg" : {
"script" : {
"id": "my_script",
"params": {
"field": "grade"
}
}
}
}
}
}
Value Script
结果发现,这次考试远远超出了学生的水平,需要进行年级修正。我们可以使用Value Script获得新的平均值:
POST /exams/_search?size=0
{
"aggs" : {
"avg_corrected_grade" : {
"avg" : {
"field" : "grade",
"script" : {
"lang": "painless",
"source": "_value * params.correction",
"params" : {
"correction" : 1.2
}
}
}
}
}
}
Missing value
missing
参数定义了如何处理缺少值的文档。默认情况下,它们将被忽略,但也可以将它们视为具有值。
POST /exams/_search?size=0
{
"aggs" : {
"grade_avg" : {
"avg" : {
"field" : "grade",
"missing": 10 #1
}
}
}
}
#1 “grade
”字段中没有值的文档将与值为10的文档属于同一个存储桶。
加权平均聚合
一种单值度量聚合,用于计算从聚合文档中提取的数值的加权平均值。这些值可以从文档中的特定数字字段中提取。
当计算常规平均值时,每个数据点都有一个相等的“权重”…它对最终值的贡献相等。另一方面,加权平均则对每个数据点进行不同的加权。每个数据点对最终值的贡献量从文档中提取,或由脚本提供。
作为公式,加权平均值是∑(value * weight) / ∑(weight)
通常的平均值可以被认为是加权平均值,其中每个值的隐式权重为1。
表3. weighted_avg
参数
参数名称 | 描述 | 需要 | 默认值 |
---|---|---|---|
| 提供值的字段或脚本的配置 | 需要 |
|
| 提供权重的字段或脚本的配置 | 需要 |
|
| 数字响应格式化程序 | 可选 |
|
| 有关纯脚本或未映射字段的值的提示 | 可选 |
|
value
和weight
对象具有每个字段特定的配置:
表4. value
参数
参数名称 | 描述 | 需要 | 默认值 |
---|---|---|---|
| 应当从中提取值的字段 | 需要 |
|
| 如果字段完全丢失,则使用的值 | 可选的 |
|
表5. weight
参数
参数名称 | 描述 | 需要 | 默认值 |
---|---|---|---|
| 应从中提取权重的字段 | 需要 |
|
| 如果字段完全丢失,则使用的权重 | 可选 |
|
范例
如果我们的文档中有一个“grade”字段包含0-100个数字分数,而“weight”字段包含任意数字权重,我们可以使用以下方法计算加权平均值:
POST /exams/_search
{
"size": 0,
"aggs" : {
"weighted_grade": {
"weighted_avg": {
"value": {
"field": "grade"
},
"weight": {
"field": "weight"
}
}
}
}
}
{
...
"aggregations": {
"weighted_grade": {
"value": 70.0
}
}
}
虽然每个字段允许多个值,但只允许一个权重。如果聚合遇到具有多个权重的文档(例如,权重字段是多值字段),它将引发异常。如果出现这种情况,则需要为权重字段指定一个脚本,并使用该脚本将多个值合并为一个要使用的值。
此单一权重将独立应用于从值字段提取的每个值。
此示例显示如何使用单个权重平均具有多个值的单个文档:
POST /exams/_doc?refresh
{
"grade": [1, 2, 3],
"weight": 2
}
POST /exams/_search
{
"size": 0,
"aggs" : {
"weighted_grade": {
"weighted_avg": {
"value": {
"field": "grade"
},
"weight": {
"field": "weight"
}
}
}
}
}
三个值(1、2和3)将作为独立值包括在内,权重均为2:
{
...
"aggregations": {
"weighted_grade": {
"value": 2.0
}
}
}
结果是聚合返回2.0,这与手工计算时的期望值相符:((1*2) + (2*2) + (3*2)) / (2+2+2) == 2
脚本
值和权重都可以从脚本而不是字段派生。作为一个简单的示例,下面将使用脚本在文档中的等级和权重中添加一个:
POST /exams/_search
{
"size": 0,
"aggs" : {
"weighted_grade": {
"weighted_avg": {
"value": {
"script": "doc.grade.value + 1"
},
"weight": {
"script": "doc.weight.value + 1"
}
}
}
}
}
missing
值
missing
参数定义应如何处理缺少值的文档。默认行为是不同的value
和weight
:
默认情况下,如果缺少该value
字段,则忽略该文档,并将聚合移至下一个文档。如果缺少该weight
字段,则假定其权重为1
(类似于正常平均值)。
可以使用以下missing
参数覆盖这两个默认值:
POST /exams/_search
{
"size": 0,
"aggs" : {
"weighted_grade": {
"weighted_avg": {
"value": {
"field": "grade",
"missing": 2
},
"weight": {
"field": "weight",
"missing": 3
}
}
}
}
}
基数聚集
单值度量聚合,计算不同值的近似计数。值可以从文档中的特定字段提取,也可以由脚本生成。
假设您正在为商店的销售建立索引,并希望计算与查询匹配的已售产品的唯一数量:
POST /sales/_search?size=0
{
"aggs" : {
"type_count" : {
"cardinality" : {
"field" : "type"
}
}
}
}
响应:
{
...
"aggregations" : {
"type_count" : {
"value" : 3
}
}
}
精度控制
此聚合还支持精度阈值选项:
POST /sales/_search?size=0
{
"aggs" : {
"type_count" : {
"cardinality" : {
"field" : "type",
"precision_threshold": 100 #1
}
}
}
}
precision_threshold
选项允许以内存为代价来交换准确性,并定义了一个唯一的计数,在此之下,预期计数将接近准确。超过此值,计数可能会变得更加模糊。支持的最大值是40000,高于此阈值的阈值将与阈值40000产生相同的效果。默认值为
3000
。
近似计数
计算精确计数需要将值加载到哈希集中并返回其大小。当处理高基数集和/或较大的值时,这无法扩展,因为所需的内存使用情况以及在节点之间传递这些每个分片集的需求会占用过多的群集资源。
此cardinality
聚合基于 HyperLogLog ++ 算法,该算法基于具有一些有趣属性的值的哈希值进行计数:
- 可配置的精度,该精度决定了如何以内存换取精度,
- 在低基数集上具有出色的准确性,
- 固定的内存使用量:无论是否有成百上千的唯一值,内存使用量仅取决于配置的精度。
对于的精确阈值c
,我们正在使用的实现大约需要c * 8
字节。
下表显示了阈值前后误差如何变化:

对于所有3个阈值,计数均精确到配置的阈值。尽管不能保证,但情况可能如此。实践中的准确性取决于所讨论的数据集。通常,大多数数据集始终显示出良好的准确性。还要注意,即使阈值低至100,即使计数数百万个项目,误差仍然非常低(如上图所示,为1-6%)。
HyperLogLog ++算法取决于散列值的前导零,数据集中散列的确切分布会影响基数的准确性。
还请注意,即使阈值低至100,即使计数数百万个项目,误差仍然非常低。
预先计算哈希值
在具有高基数的字符串字段上,将字段值的哈希存储在索引中然后在此字段上运行基数聚合可能会更快。这可以通过从客户端提供哈希值来完成,也可以让Elasticsearch使用mapper-murmur3
插件为您计算哈希值来完成 。
预计算哈希通常仅在非常大和/或高基数的字段上有用,因为它可以节省CPU和内存。但是,在数字字段上,散列非常快,存储原始值所需的内存与存储散列的内存相同或更少。对于低基数的字符串字段也是如此,特别是考虑到这些字段已进行了优化,以确保每个段的每个唯一值最多计算一次哈希。
script
该cardinality
指标支持脚本编写,但性能显着降低,因为需要动态计算哈希值。
POST /sales/_search?size=0
{
"aggs" : {
"type_promoted_count" : {
"cardinality" : {
"script": {
"lang": "painless",
"source": "doc['type'].value + ' ' + doc['promoted'].value"
}
}
}
}
}
这将把script
参数解释为使用painless
脚本语言的内联脚本,而不使用脚本参数。要使用存储的脚本,请使用以下语法:
POST /sales/_search?size=0
{
"aggs" : {
"type_promoted_count" : {
"cardinality" : {
"script" : {
"id": "my_script",
"params": {
"type_field": "type",
"promoted_field": "promoted"
}
}
}
}
}
}
Missing值
missing
参数定义应如何处理缺少值的文档。默认情况下,它们将被忽略,但也可以将它们视为具有值。
POST /sales/_search?size=0
{
"aggs" : {
"tag_cardinality" : {
"cardinality" : {
"field" : "tag",
"missing": "N/A" #1
}
}
}
}
#1 tag
字段中没有值的文档将与值为N/a的文档属于同一个bucket。
扩展统计聚合
一个multi-value
指标聚集了从聚集的文档中提取的数值,计算统计。这些值可以从文档中的特定数字字段中提取,也可以由提供的脚本生成。
所述extended_stats
聚合是的扩展版本stats
聚集,其中附加的度量被加到如sum_of_squares
,variance
,std_deviation
和std_deviation_bounds
。
假设数据由代表学生考试成绩(0至100)的文档组成
GET /exams/_search
{
"size": 0,
"aggs" : {
"grades_stats" : { "extended_stats" : { "field" : "grade" } }
}
}
以上聚合计算所有文档的等级统计信息。聚合类型是extended_stats,字段设置定义要计算统计信息的文档的数值字段。以上将返回以下内容:
{
...
"aggregations": {
"grades_stats": {
"count": 2,
"min": 50.0,
"max": 100.0,
"avg": 75.0,
"sum": 150.0,
"sum_of_squares": 12500.0,
"variance": 625.0,
"std_deviation": 25.0,
"std_deviation_bounds": {
"upper": 125.0,
"lower": 25.0
}
}
}
}
可以从返回的响应中检索聚合结果。
标准差边界
默认情况下,扩展的_stats度量将返回一个名为std_deviation_bounds的对象,该对象提供与平均值的正负两个标准偏差的间隔。这是可视化数据变化的有用方法。如果需要不同的边界,例如三个标准差,可以在请求中设置sigma:
GET /exams/_search
{
"size": 0,
"aggs" : {
"grades_stats" : {
"extended_stats" : {
"field" : "grade",
"sigma" : 3 #1
}
}
}
}
#1 sigma
控制应该显示多少个与均值+/-的标准偏差
sigma
可以是任何非负双精度值,这意味着您可以请求非整数值,例如1.5
。值0
有效,但只会返回边界upper
和lower
边界的平均值。
标准偏差和界限需要正态性
默认情况下会显示标准差及其界限,但它们并不总是适用于所有数据集。您的数据必须正态分布以使指标有意义。标准差背后的统计数据假设数据呈正态分布,因此,如果您的数据偏左或偏右,则返回的值将产生误导。
脚本
根据脚本计算成绩统计信息:
GET /exams/_search
{
"size": 0,
"aggs" : {
"grades_stats" : {
"extended_stats" : {
"script" : {
"source" : "doc['grade'].value",
"lang" : "painless"
}
}
}
}
}
这将把脚本参数解释为使用无痛脚本语言的内联脚本,而不使用脚本参数。要使用存储的脚本,请使用以下语法:
GET /exams/_search
{
"size": 0,
"aggs" : {
"grades_stats" : {
"extended_stats" : {
"script" : {
"id": "my_script",
"params": {
"field": "grade"
}
}
}
}
}
}
值脚本
结果发现,这次考试远远超出了学生的水平,需要进行年级修正。我们可以使用值脚本获取新的统计信息:
GET /exams/_search
{
"size": 0,
"aggs" : {
"grades_stats" : {
"extended_stats" : {
"field" : "grade",
"script" : {
"lang" : "painless",
"source": "_value * params.correction",
"params" : {
"correction" : 1.2
}
}
}
}
}
}
缺失值
missing参数定义了如何处理缺少值的文档。默认情况下,它们将被忽略,但也可以将它们视为具有值。
GET /exams/_search
{
"size": 0,
"aggs" : {
"grades_stats" : {
"extended_stats" : {
"field" : "grade",
"missing": 0 #1
}
}
}
}
#1 “grade
”字段中没有值的文档将与值为0的文档属于同一个存储桶。
地理边界聚合
一种度量标准聚合,用于计算包含一个字段的所有geo_point值的边界框。
例:
PUT /museums
{
"mappings": {
"properties": {
"location": {
"type": "geo_point"
}
}
}
}
POST /museums/_bulk?refresh
{"index":{"_id":1}}
{"location": "52.374081,4.912350", "name": "NEMO Science Museum"}
{"index":{"_id":2}}
{"location": "52.369219,4.901618", "name": "Museum Het Rembrandthuis"}
{"index":{"_id":3}}
{"location": "52.371667,4.914722", "name": "Nederlands Scheepvaartmuseum"}
{"index":{"_id":4}}
{"location": "51.222900,4.405200", "name": "Letterenhuis"}
{"index":{"_id":5}}
{"location": "48.861111,2.336389", "name": "Musée du Louvre"}
{"index":{"_id":6}}
{"location": "48.860000,2.327000", "name": "Musée d'Orsay"}
POST /museums/_search?size=0
{
"query" : {
"match" : { "name" : "musée" }
},
"aggs" : {
"viewport" : {
"geo_bounds" : {
"field" : "location",
"wrap_longitude" : true
}
}
}
}
#1 geo_bounds聚合指定用于获取边界的字段
#2 wrap_longitude是一个可选参数,指定是否允许边界框重叠国际日期行。默认值为true
上面的聚合展示了如何为具有商店业务类型的所有文档计算位置字段的边界框
对上述聚合的响应:
{
...
"aggregations": {
"viewport": {
"bounds": {
"top_left": {
"lat": 48.86111099738628,
"lon": 2.3269999679178
},
"bottom_right": {
"lat": 48.85999997612089,
"lon": 2.3363889567553997
}
}
}
}
}
地理中心聚合
一种度量聚合,它根据Geo-point字段的所有坐标值计算加权中心。
例:
PUT /museums
{
"mappings": {
"properties": {
"location": {
"type": "geo_point"
}
}
}
}
POST /museums/_bulk?refresh
{"index":{"_id":1}}
{"location": "52.374081,4.912350", "city": "Amsterdam", "name": "NEMO Science Museum"}
{"index":{"_id":2}}
{"location": "52.369219,4.901618", "city": "Amsterdam", "name": "Museum Het Rembrandthuis"}
{"index":{"_id":3}}
{"location": "52.371667,4.914722", "city": "Amsterdam", "name": "Nederlands Scheepvaartmuseum"}
{"index":{"_id":4}}
{"location": "51.222900,4.405200", "city": "Antwerp", "name": "Letterenhuis"}
{"index":{"_id":5}}
{"location": "48.861111,2.336389", "city": "Paris", "name": "Musée du Louvre"}
{"index":{"_id":6}}
{"location": "48.860000,2.327000", "city": "Paris", "name": "Musée d'Orsay"}
POST /museums/_search?size=0
{
"aggs" : {
"centroid" : {
"geo_centroid" : {
"field" : "location"
}
}
}
}
#1 该geo_centroid
集合指定现场使用用于计算中心。(注意:字段必须是Geo-point类型)
上面的聚合显示了如何计算犯罪类型为盗窃的所有文档的位置字段的中心
以上聚合的响应:
{
...
"aggregations": {
"centroid": {
"location": {
"lat": 51.00982965203002,
"lon": 3.9662131341174245
},
"count": 6
}
}
}
当组合为其他bucket聚合的子聚合时,geo_centroid聚合更有趣。
例子:
POST /museums/_search?size=0
{
"aggs" : {
"cities" : {
"terms" : { "field" : "city.keyword" },
"aggs" : {
"centroid" : {
"geo_centroid" : { "field" : "location" }
}
}
}
}
}
上面的示例使用geo_centroid作为术语bucket聚合的子聚合,用于查找每个城市中博物馆的中心位置。
对上述聚合的响应:
{
...
"aggregations": {
"cities": {
"sum_other_doc_count": 0,
"doc_count_error_upper_bound": 0,
"buckets": [
{
"key": "Amsterdam",
"doc_count": 3,
"centroid": {
"location": {
"lat": 52.371655656024814,
"lon": 4.909563297405839
},
"count": 3
}
},
{
"key": "Paris",
"doc_count": 2,
"centroid": {
"location": {
"lat": 48.86055548675358,
"lon": 2.3316944623366
},
"count": 2
}
},
{
"key": "Antwerp",
"doc_count": 1,
"centroid": {
"location": {
"lat": 51.22289997059852,
"lon": 4.40519998781383
},
"count": 1
}
}
]
}
}
}
最大聚合
一个单值度量聚合,它跟踪并返回从聚合文档中提取的数值之间的最大值。这些值可以从文档中的特定数字字段中提取,也可以由提供的脚本生成。
最小和最大聚合对数据的双重表示进行操作。结果,当在绝对值大于2 ^ 53的长边上运行时,结果可能是近似的。
计算所有文档的最大价格值
POST /sales/_search?size=0
{
"aggs" : {
"max_price" : { "max" : { "field" : "price" } }
}
}
响应:
{
...
"aggregations": {
"max_price": {
"value": 200.0
}
}
}
可以看到,聚合的名称(上面的max_price)也是从返回的响应中检索聚合结果的键。
脚本
MAX聚合还可以计算脚本的最大值。下面的例子计算最大价格:
POST /sales/_search
{
"aggs" : {
"max_price" : {
"max" : {
"script" : {
"source" : "doc.price.value"
}
}
}
}
}
这将使用无痛脚本语言,并且没有脚本参数。要使用存储的脚本,请使用以下语法:
POST /sales/_search
{
"aggs" : {
"max_price" : {
"max" : {
"script" : {
"id": "my_script",
"params": {
"field": "price"
}
}
}
}
}
}
值脚本
假设我们索引中文档的价格是美元,但是我们想计算欧元的最大值(为了这个例子,假设转换率是1.2)。我们可以使用值脚本将转换率应用于聚合前的每个值:
POST /sales/_search
{
"aggs" : {
"max_price_in_euros" : {
"max" : {
"field" : "price",
"script" : {
"source" : "_value * params.conversion_rate",
"params" : {
"conversion_rate" : 1.2
}
}
}
}
}
}
缺失值
missing参数定义了如何处理缺少值的文档。默认情况下,它们将被忽略,但也可以将它们视为具有值。
POST /sales/_search
{
"aggs" : {
"grade_max" : {
"max" : {
"field" : "grade",
"missing": 10 #1
}
}
}
}
#1 “grade”字段中没有值的文档将与值为10的文档属于同一个存储桶。
最小值聚合
单一值度量聚合,跟踪并返回从聚合文档中提取的数值中的最小值。这些值可以从文档中的特定数字字段中提取,也可以由提供的脚本生成。
最小和最大聚合对数据的双重表示进行操作。结果,当在绝对值大于2 ^ 53的长边上运行时,结果可能是近似的。
计算所有文档的最小价格值:
POST /sales/_search?size=0
{
"aggs" : {
"min_price" : { "min" : { "field" : "price" } }
}
}
响应:
{
...
"aggregations": {
"min_price": {
"value": 10.0
}
}
}
可以看到,聚合的名称(上面的min_price)也是从返回的响应中检索聚合结果的键。
脚本
最小聚合还可以计算脚本的最小值。以下示例计算最低价格:
POST /sales/_search
{
"aggs" : {
"min_price" : {
"min" : {
"script" : {
"source" : "doc.price.value"
}
}
}
}
}
这将使用无痛脚本语言,并且没有脚本参数。要使用存储的脚本,请使用以下语法:
POST /sales/_search
{
"aggs" : {
"min_price" : {
"min" : {
"script" : {
"id": "my_script",
"params": {
"field": "price"
}
}
}
}
}
}
值脚本
假设我们索引中文档的价格是以美元为单位的,但是我们想计算以欧元为单位的最小值(为了这个例子,假设转换率是1.2)。我们可以使用值脚本将转换率应用于聚合前的每个值:
POST /sales/_search
{
"aggs" : {
"min_price_in_euros" : {
"min" : {
"field" : "price",
"script" : {
"source" : "_value * params.conversion_rate",
"params" : {
"conversion_rate" : 1.2
}
}
}
}
}
}
缺失值
missing的参数定义了如何处理缺少值的文档。默认情况下,它们将被忽略,但也可以将它们视为具有值。
POST /sales/_search
{
"aggs" : {
"grade_min" : {
"min" : {
"field" : "grade",
"missing": 10 #1
}
}
}
}
#1 “grade”字段中没有值的文档将与值为10的文档属于同一个存储桶。
百分比聚合
一种多值度量聚合,计算从聚合文档中提取的数值上的一个或多个百分位数。这些值可以从文档中的特定数字字段中提取,也可以由提供的脚本生成。
百分位数表示观察值的某个百分比出现的点。例如,95%是大于观察值95%的值。
百分位数通常用于查找异常值。在正态分布中,0.13%和99.87%代表三个与平均值的标准差。任何超出三个标准差的数据通常被视为异常。
当一系列的百分位数被检索到时,它们可以用来估计数据分布,并确定数据是否是倾斜的、双峰的等。
假设您的数据包含网站加载时间。对于管理员来说,平均加载时间和中间加载时间不是很有用。最大值可能很有趣,但它很容易被一个缓慢的响应所扭曲。
让我们看看代表加载时间的百分比范围:
GET latency/_search
{
"size": 0,
"aggs" : {
"load_time_outlier" : {
"percentiles" : {
"field" : "load_time" #1
}
}
}
}
#1 字段load_time必须是数字字段
默认情况下,百分比度量将生成百分比范围:[1、5、25、50、75、95、99]。响应如下:
{
...
"aggregations": {
"load_time_outlier": {
"values" : {
"1.0": 5.0,
"5.0": 25.0,
"25.0": 165.0,
"50.0": 445.0,
"75.0": 725.0,
"95.0": 945.0,
"99.0": 985.0
}
}
}
}
如您所见,聚合将返回默认范围内每个百分比的计算值。如果我们假设响应时间以毫秒为单位,那么很明显,网页通常加载时间为10-725ms,但偶尔会飙升到945-985ms。
通常,管理员只对异常值(极端百分位数)感兴趣。我们可以只指定感兴趣的百分比(请求的百分比必须是介于0到100之间的值,包括0到100):
GET latency/_search
{
"size": 0,
"aggs" : {
"load_time_outlier" : {
"percentiles" : {
"field" : "load_time",
"percents" : [95, 99, 99.9] #1
}
}
}
}
#1 使用percents参数指定要计算的特定百分比
键控响应
默认情况下,keyed标志设置为true,它将一个唯一的字符串键与每个bucket相关联,并将范围作为散列而不是数组返回。将keyed标志设置为false将禁用此行为:
GET latency/_search
{
"size": 0,
"aggs": {
"load_time_outlier": {
"percentiles": {
"field": "load_time",
"keyed": false
}
}
}
}
响应:
{
...
"aggregations": {
"load_time_outlier": {
"values": [
{
"key": 1.0,
"value": 5.0
},
{
"key": 5.0,
"value": 25.0
},
{
"key": 25.0,
"value": 165.0
},
{
"key": 50.0,
"value": 445.0
},
{
"key": 75.0,
"value": 725.0
},
{
"key": 95.0,
"value": 945.0
},
{
"key": 99.0,
"value": 985.0
}
]
}
}
}
脚本
百分位数度量支持脚本。例如,如果加载时间以毫秒为单位,但我们希望以秒为单位计算百分位数,则可以使用脚本动态转换它们:
GET latency/_search
{
"size": 0,
"aggs" : {
"load_time_outlier" : {
"percentiles" : {
"script" : {
"lang": "painless",
"source": "doc['load_time'].value / params.timeUnit", #1
"params" : {
"timeUnit" : 1000 #2
}
}
}
}
}
}
#1 field参数替换为script参数,该参数使用脚本生成在其上计算百分位数的值
#2 脚本支持参数化输入,就像其他脚本一样
这将把脚本参数解释为使用无痛脚本语言的内联脚本,而不使用脚本参数。要使用存储的脚本,请使用以下语法:
GET latency/_search
{
"size": 0,
"aggs" : {
"load_time_outlier" : {
"percentiles" : {
"script" : {
"id": "my_script",
"params": {
"field": "load_time"
}
}
}
}
}
}
百分位数(通常情况下)是近似的
有许多不同的算法来计算百分位数。naive实现只是将所有值存储在排序数组中。要找到第50个百分位数,只需找到my_array[count(my_array) * 0.5]
处的值。
显然,这种简单的实现不会缩放排序数组,它会随着数据集中值的数量线性增长。为了计算在Elasticsearch群中跨越潜在十亿值的百分位数,计算近似百分位数。
percentile
度量使用的算法称为TDigest(由Ted Dunning在使用T-摘要计算准确的分位数时引入)。
使用此指标时,需要记住以下几条准则:
- 精度与
q(1-q)
。这意味着极端百分位数(例如99%)比较低极端百分位数(例如中值)更准确 - 对于较小的一组值,百分位数非常准确(如果数据足够小,则可能为100%准确)。
- 当桶中的数值量增长时,算法开始近似百分位数。它有效地用精确性换取内存节省。不准确的确切程度很难概括,因为它取决于数据分布和被聚合的数据量
下表显示了均匀分布上的相对误差,具体取决于收集值的数量和请求的百分比:
它显示了极端百分位数的精度如何更高。误差减小的原因在于,大量定律使得值的分布越来越均匀,而t-摘要树可以更好地概括它。在更偏斜的分布上情况并非如此。
百分位数聚合也是 不确定的。这意味着使用相同的数据可以获得略有不同的结果。
压缩
近似算法必须在存储器利用率和估计精度之间取得平衡。可以使用以下compression
参数控制此余额:
GET latency/_search
{
"size": 0,
"aggs" : {
"load_time_outlier" : {
"percentiles" : {
"field" : "load_time",
"tdigest": {
"compression" : 200 #1
}
}
}
}
}
#1 压缩控制内存使用率和近似误差
TDigest算法使用多个“节点”来近似百分位数,即可用的节点越多,精度(和大内存占用)越高,与数据量成正比。compression
参数将节点的最大数目限制为20 * compression
。
因此,通过增加压缩值,您可以以消耗更多内存为代价提高百分位数的精度。较大的压缩值也会使算法变慢,因为底层树数据结构的大小会增加,从而导致更昂贵的操作。默认压缩值为100。
一个“节点”使用大约32字节的内存,因此在最坏的情况下(大量数据按顺序到达)默认设置将产生大约64KB大小的TDigest。实际上,数据往往更随机,而TDigest将使用更少的内存。
HDR直方图
此设置公开了HDR直方图的内部实现,语法将来可能会更改。
HDR直方图(High Dynamic Range Histogram)是一种替代实现,在计算延迟测量的百分位数时非常有用,因为它可以比t-digest实现更快,同时兼顾更大的内存占用。此实现维护一个固定的更坏情况百分比错误(指定为有效数字的数目)。这意味着,如果数据被记录在从1微秒到1小时(3600000000微秒)的值中的直方图设置为3个有效数字,它将保持1微秒的值分辨率达1毫秒和3.6秒(或更好)的最大跟踪值(1小时)。
可以通过method
在请求中指定参数来使用HDR直方图:
GET latency/_search
{
"size": 0,
"aggs" : {
"load_time_outlier" : {
"percentiles" : {
"field" : "load_time",
"percents" : [95, 99, 99.9],
"hdr": { #1
"number_of_significant_value_digits" : 3 #2
}
}
}
}
}
#1 hdr
object表示应使用HDR直方图来计算百分位数,并且可以在对象内部指定此算法的特定设置
#2 number_of_significant_value_digits
以有效位数指定直方图的值的分辨率
HDRHistogram只支持正值,如果传递负值,它将出错。如果值的范围未知,那么使用HDRHistogram也不是一个好主意,因为这可能会导致高内存使用率。
缺失值
missing
参数定义应如何处理缺少值的文档。默认情况下,它们将被忽略,但也可以将它们视为具有值。
GET latency/_search
{
"size": 0,
"aggs" : {
"grade_percentiles" : {
"percentiles" : {
"field" : "grade",
"missing": 10 #1
}
}
}
}
#1 grade
字段中没有值的文档将与具有该值的文档归入同一存储桶10
。