聚合框架有助于提供基于搜索查询的聚合数据。它基于称为聚合的简单构建块,可以对其进行组合以构建复杂的数据摘要。
聚合可以被看作是在一组文档上构建分析信息的工作单元。执行的上下文定义了该文档集是什么(例如,顶级聚合在执行的查询/搜索请求过滤器的上下文内执行)。
有许多不同类型的聚合,每一种都有自己的目的和输出。为了更好地理解这些类型,通常更容易将它们分为四大类:
构建存储桶的聚合系列,其中每个存储桶都与一个键和一个文档标准相关联。执行聚合时,将对上下文中的每个文档评估所有存储桶标准,并且当标准匹配时,该文档被视为“落入”相关存储桶。在聚合过程结束时,我们将得到一个桶列表——每个桶都有一组“属于”它的文档。
跟踪和计算一组文档的指标的聚合。
对多个字段进行操作并根据从请求的文档字段中提取的值生成矩阵结果的聚合系列。与指标和存储桶聚合不同,此聚合系列尚不支持脚本。
聚合其他聚合的输出及其相关指标的聚合
接下来是有趣的部分。由于每个存储桶有效地定义了一个文档集(属于该存储桶的所有文档),因此可以潜在地关联存储桶级别的聚合,并且这些聚合将在该存储桶的上下文中执行。这就是聚合的真正威力发挥作用的地方:聚合可以嵌套!
构建聚合
以下代码段包含了聚合的基本结构:
"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
聚合键设置的特定文档字段中提取。也可以定义一个将生成值的(每个文档)。
当field
和script
设置都为聚合配置时,脚本将被视为 value script
. 普通脚本在文档级别进行评估(即脚本可以访问与文档相关的所有数据),而值脚本在值级别进行评估。在这种模式中,从配置field
和script
提取的值用于通过这些值的“转化”。
NOTE
使用脚本时,还可以定义lang
和params
设置。前者定义了所使用的脚本语言(假设 Elasticsearch 中提供了正确的语言,默认情况下或作为插件)。后者允许将脚本中的所有“动态”表达式定义为参数,这使脚本能够在调用之间保持自身静态(这将确保在 Elasticsearch 中使用缓存的编译脚本)。
Elasticsearch 使用映射中的字段类型来确定如何运行聚合和格式化响应。但是,在两种情况下 Elasticsearch 无法计算出这些信息:未映射的字段(例如在跨多个索引的搜索请求的情况下,并且只有其中一些具有该字段的映射)和纯脚本。对于这些情况,可以使用value_type
选项为 Elasticsearch 提供提示,该选项接受以下值:string
, long
(适用于所有整数类型)、 double
(适用于所有小数类型,如float
或scaled_float
)date
、 ip
和boolean
。
指标聚合
此系列中的聚合基于从正在聚合的文档中以一种或另一种方式提取的值来计算度量。这些值通常从文档的字段中提取(使用字段数据),但也可以使用脚本生成。
数字度量聚合是一种特殊类型的度量聚合,它输出数值。一些聚合输出单个数字度量(例如avg
)称为single-value numeric metrics aggregation
,其他聚合生成多个度量(例如stats
)称为multi-value numeric metrics aggregation
。当这些聚合用作某些存储桶聚合的直接子聚合时(某些存储桶聚合使您能够根据每个存储桶中的数字度量对返回的存储桶进行排序),单值和多值数字度量聚合之间的区别会起作用。
平均聚合
一种single-value
度量聚合,用于计算从聚合文档中提取的数值的平均值。这些值可以从文档中的特定数字字段中提取,也可以由提供的脚本生成。
假设数据由代表学生考试成绩(0 到 100 之间)的文件组成,我们可以用以下方法平均他们的分数:
POST /exams/_search?size=0
{
"aggs" : {
"avg_grade" : { "avg" : { "field" : "grade" } }
}
}
上述聚合计算所有文档的平均等级。聚合类型是avg
,并且field
设置定义了将计算平均值的文档的数字字段。以上将返回以下内容:
{
...
"aggregations": {
"avg_grade": {
"value": 75.0
}
}
}
聚合的名称(avg_grade
如上)也用作可以从返回的响应中检索聚合结果的键。
根据脚本计算平均成绩:
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"
}
}
}
}
}
}
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
}
}
}
}
| 该grade 字段中没有值的文档将与具有该值为10 的文档落入同一个桶中。 |
加权平均聚合
一种single-value
度量聚合,用于计算从聚合文档中提取的数值的加权平均值。这些值可以从文档中的特定数字字段中提取。
在计算常规平均值时,每个数据点都有相同的“权重”……它对最终值的贡献相同。另一方面,加权平均值对每个数据点的权重不同。每个数据点对最终值的贡献从文档中提取,或由脚本提供。
作为一个公式,加权平均是 ∑(value * weight) / ∑(weight)
可以将常规平均值视为加权平均值,其中每个值的隐含权重为1
。
表 3.weighted_avg
参数
参数名称 | 描述 | 必需的 | 默认值 |
---|
value | 提供值的字段或脚本的配置 | 必需的 | |
weight | 提供权重的字段或脚本的配置 | 必需的 | |
format | 数字响应格式化程序 | 可选的 | |
value_type | 关于纯脚本或未映射字段的值的提示 | 可选的 | |
在value
和weight
对象有每场具体配置:
表 4.value
参数
参数名称 | 描述 | 必需的 | 默认值 |
---|
field | 应从中提取值的字段 | 必需的 | |
missing | 如果字段完全丢失,使用该参数值 | 可选的 | |
表 5.weight
参数
参数名称 | 描述 | 必需的 | 默认值 |
---|
field | 应从中提取权重的字段 | 必需的 | |
missing | 如果该字段完全丢失,则使用权重 | 可选的 | |
如果我们的文档有一个包含 0-100 数字分数的"grade"
字段,
和一个包含任意数字权重的"weight"
字段,我们可以使用以下方法计算加权平均值:
POST /exams/_search
{
"size": 0,
"aggs" : {
"weighted_grade": {
"weighted_avg": {
"value": {
"field": "grade"
},
"weight": {
"field": "weight"
}
}
}
}
}
这会产生如下响应:
{
...
"aggregations": {
"weighted_grade": {
"value": 70.0
}
}
}
虽然每个字段允许有多个值,但只允许一个权重。如果聚合遇到具有多个权重的文档(例如,权重字段是多值字段),它将抛出异常。如果有这种情况,需要script
为权重指定一个字段,并使用脚本将多个值组合成一个单独的值来使用。
这个单一的权重将应用于从value
字段中提取的每个值。
此示例显示了如何使用单个权重对具有多个值的单个文档进行平均:
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
值和权重都可以从脚本中导出,而不是从字段中导出。作为一个简单的例子,下面将使用脚本为文档中添加grade和weight:
POST /exams/_search
{
"size": 0,
"aggs" : {
"weighted_grade": {
"weighted_avg": {
"value": {
"script": "doc.grade.value + 1"
},
"weight": {
"script": "doc.weight.value + 1"
}
}
}
}
}
该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
}
}
}
}
}
基数聚合
single-value
度量聚集,计算不同的值的近似计数。值可以从文档中的特定字段中提取或由脚本生成。
假设正在索引商店销售额并希望计算与查询匹配的已售产品的唯一数量:
POST /sales/_search?size=0
{
"aggs" : {
"type_count" : {
"cardinality" : {
"field" : "type"
}
}
}
}
回复:
{
...
"aggregations" : {
"type_count" : {
"value" : 3
}
}
}
此聚合还支持以下precision_threshold
选项:
POST /sales/_search?size=0
{
"aggs" : {
"type_count" : {
"cardinality" : {
"field" : "type",
"precision_threshold": 100
}
}
}
}
precision_threshold
选项允许用内存来换取准确性,并定义一个唯一计数,低于该计数预计将接近准确。高于此值,计数可能会变得更加模糊。支持的最大值为 40000,高于此数字的阈值将与阈值 40000 具有相同的效果。默认值为3000
。
计算精确计数需要将值加载到散列集并返回其大小。在处理高基数集和/或大数据量时,这不会扩展,因为所需的内存使用量以及在节点之间通信这些每个分片集会使用集群太多资源。
cardinality
聚合基于 算法,该算法基于具有一些指定属性的值的哈希值进行计数:
- 可配置的精度,该精度决定如何用内存来换取精度
- 在低基数集上具有出色的准确性,
- 固定内存使用:无论是数百还是数十亿个唯一值,内存使用仅取决于配置的精度。
对于精度阈值c
,正在使用的实现需要大约c * 8
字节。
下图显示了阈值前后误差的变化情况:
对于所有 3 个阈值,计数一直精确到配置的阈值。虽然不能保证,但很可能就是这种情况。实践中的准确性取决于所讨论的数据集。一般来说,大多数数据集始终显示出良好的准确性。另请注意,即使阈值低至 100,即使在计算数百万个项目时,错误仍然非常低(如上图所示为 1-6%)。
HyperLogLog++ 算法取决于散列值的前导零,数据集中散列的确切分布会影响基数的准确性。
还请注意,即使阈值低至 100,错误仍然非常低,即使在计算数百万个项目时也是如此。
在具有高基数的字符串字段上,将字段值的散列存储在索引中然后在该字段上运行基数聚合可能会更快。这可以通过从客户端提供哈希值或让 Elasticsearch 使用插件计算哈希值来完成 。
NOTE
预计算散列通常只对非常大和/或高基数的字段有用,因为它可以节省 CPU 和内存。但是,在数字字段上,散列非常快,并且存储原始值所需的内存与存储散列所需的内存一样多或少。对于低基数字符串字段也是如此,特别是考虑到那些具有优化以确保每个段的每个唯一值最多计算一次哈希值的情况。
cardinality
指标支持脚本,但性能受影响明显,因为需要即时计算哈希值。
POST /sales/_search?size=0
{
"aggs" : {
"type_promoted_count" : {
"cardinality" : {
"script": {
"lang": "painless",
"source": "doc['type'].value + ' ' + doc['promoted'].value"
}
}
}
}
}
这会将script
参数解释为inline
带有painless
脚本语言且没有脚本参数的脚本。要使用存储的脚本,请使用以下语法:
POST /sales/_search?size=0
{
"aggs" : {
"type_promoted_count" : {
"cardinality" : {
"script" : {
"id": "my_script",
"params": {
"type_field": "type",
"promoted_field": "promoted"
}
}
}
}
}
}
该missing
参数定义应如何处理缺少值的文档。默认情况下,它们将被忽略,但也可以将它们视为具有值。
POST /sales/_search?size=0
{
"aggs" : {
"tag_cardinality" : {
"cardinality" : {
"field" : "tag",
"missing": "N/A"
}
}
}
}
该tag
字段中没有值的文档将与具有N/A
的文档落入同一个桶中。
扩展统计聚合
一种multi-value
度量聚合,它计算从聚合文档中提取的数值的统计信息。这些值可以从文档中的特定数字字段中提取,也可以由提供的脚本生成。
extended_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
,并且field
设置定义了将在其上计算统计信息的文档的数字字段。以上将返回以下内容:
{
...
"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
}
}
}
}
聚合的名称(如上grades_stats
)也用作可以从返回的响应中检索聚合结果的键。
默认情况下,extended_stats
指标将返回一个名为std_deviation_bounds
的对象,它提供与平均值正负两个标准差的区间。这可能是一种可视化数据方差的有用方法。如果想要不同的边界,例如三个标准偏差,可以在请求中设置sigma
:
GET /exams/_search
{
"size": 0,
"aggs" : {
"grades_stats" : {
"extended_stats" : {
"field" : "grade",
"sigma" : 3
}
}
}
}
sigma
控制应该显示多少标准差 +/-。
sigma
可以是任何非负双精度值,这意味着可以请求非整数值,例如1.5
。0
也是有效的,但只会返回upper
和lower
边界的平均值。
NOTE
标准偏差和界限需要正态性
默认情况下显示标准偏差及其界限,但它们并不总是适用于所有数据集。数据必须正态分布才能使指标有意义。标准差背后的统计数据假设数据呈正态分布,因此如果数据严重向左或向右倾斜,则返回的值将具有误导性。
基于脚本计算成绩统计信息:
GET /exams/_search
{
"size": 0,
"aggs" : {
"grades_stats" : {
"extended_stats" : {
"script" : {
"source" : "doc['grade'].value",
"lang" : "painless"
}
}
}
}
}
这会将script
参数解释为inline
带有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
}
}
}
}
该grade
字段中没有值的文档将与具有0
的文档落入同一个桶中。
Geo Bounds聚合
计算所有包含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.3363", "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
}
}
}
}
该geo_bounds 集合指定为location,以获得界限 |
wrap_longitude 是一个可选参数,它指定是否允许边界框与国际日期变更线重叠。默认值为true |
上面的聚合演示了如何为具有商店业务类型的所有文档计算位置字段的边界框
上述聚合的响应:
{
...
"aggregations": {
"viewport": {
"bounds": {
"top_left": {
"lat": 48.86111099738628,
"lon": 2.3269999679178
},
"bottom_right": {
"lat": 48.859999976120,
"lon": 2.33638567553997
}
}
}
}
}
地理质心聚合
一种度量聚合,它根据字段的所有坐标值计算加权。
例子:
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.3363", "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"
}
}
}
}
该geo_centroid 集合指定location用于计算质心。(注意:字段必须是类型) |
上面的聚合演示了如何计算所有犯罪类型为入室盗窃的文档的位置字段的质心
上述聚合的响应:
{
...
"aggregations": {
"centroid": {
"location": {
"lat": 51.00982965203002,
"lon": 3.9662131341174245
},
"count": 6
}
}
}
当geo_centroid
组合为其他桶聚合的子聚合时,聚合会更有趣。
例子:
POST /museums/_search?size=0
{
"aggs" : {
"cities" : {
"terms" : { "field" : "city.keyword" },
"aggs" : {
"centroid" : {
"geo_centroid" : { "field" : "location" }
}
}
}
}
}
上面的示例geo_centroid
用作桶聚合的子聚合, 用于查找每个城市博物馆的中心位置。
上述聚合的响应:
{
...
"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.860558675358,
"lon": 2.3316944623366
},
"count": 2
}
},
{
"key": "Antwerp",
"doc_count": 1,
"centroid": {
"location": {
"lat": 51.222997059852,
"lon": 4.40519998781383
},
"count": 1
}
}
]
}
}
}
警告
使用geo_centroid
作为一个子聚集geohash_grid
聚集将整个文档而不是单个geo-points放入桶中。如果文档的geo_point
字段包含,则可以将文档分配给多个存储桶,即使其一个或多个地理点在存储桶边界之外。
如果geocentroid
还使用子聚合,则使用桶中的所有地理点计算每个质心,包括桶边界之外的地理点。这可能导致质心超出桶边界。
最大聚合
一种single-value
度量聚合,它跟踪并返回从聚合文档中提取的数值中的最大值。这些值可以从文档中的特定数字字段中提取,也可以由提供的脚本生成。
NOTE
min
和max
聚合操作的数据以double
形态呈现。因此,在绝对值大于2^53
的 long 上运行时,结果可能是近似的。
计算所有文档的最大价格值
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
}
}
}
}
该grade 字段中没有值的文档将与该值为10的文档落入同一个桶中。 |
最小聚合
一种single-value
度量聚合,它跟踪并返回从聚合文档中提取的数值中的最小值。这些值可以从文档中的特定数字字段中提取,也可以由提供的脚本生成。
NOTE
min
和max
聚合操作的数据以double
形态呈现。因此,在绝对值大于2^53
的 long 上运行时,结果可能是近似的。
计算所有文档的最低价格值:
POST /sales/_search?size=0
{
"aggs" : {
"min_price" : { "min" : { "field" : "price" } }
}
}
响应:
{
...
"aggregations": {
"min_price": {
"value": 10.0
}
}
}
可以看出,聚合的名称(如上min_price
)也作为可以从返回的响应中检索聚合结果的键。
该min
聚合还可以通过脚本计算最小值。下面的示例计算最低价格:
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
}
}
}
}
该grade 字段中没有值的文档将与该值为10 的文档落入同一个桶中。 |
百分位数聚合
一种multi-value
度量聚合,它计算从聚合文档中提取的数值的一个或多个百分位数。这些值可以由提供的脚本生成,也可以从文档中的特定数字或中提取 。
百分位数显示出现一定百分比的观察值的点。例如,第 95 个百分位数是大于 95% 的观测值。
百分位数通常用于查找异常值。在正态分布中,第 0.13 和第 99.87 个百分位数表示与平均值的三个标准差。任何超出三个标准差的数据通常都被视为异常。
当检索到一系列百分位数时,它们可用于估计数据分布并确定数据是否偏斜、双峰等。
假设数据包含网站加载时间。平均和中值加载时间对管理员来说并没有多大用。最大值可能很有趣,但它很容易被单个缓慢的响应所扭曲。
让我们看看代表加载时间的百分位数范围:
GET latency/_search
{
"size": 0,
"aggs" : {
"load_time_outlier" : {
"percentiles" : {
"field" : "load_time"
}
}
}
}
默认情况下,该percentile
指标将生成一个百分位数范围:[ 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-725 毫秒内加载,但偶尔会达到 945-985 毫秒。
通常,管理员只对异常值感兴趣——极端的百分位数。我们可以只指定我们感兴趣的百分比(请求的百分比必须是 0-100 之间的值):
GET latency/_search
{
"size": 0,
"aggs" : {
"load_time_outlier" : {
"percentiles" : {
"field" : "load_time",
"percents" : [95, 99, 99.9]
}
}
}
}
默认情况下,该keyed
标志设置为true
将唯一的字符串键与每个存储桶相关联,并将范围作为散列而不是数组返回。将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",
"params" : {
"timeUnit" : 1000
}
}
}
}
}
}
该field 参数替换为一个script 参数,该参数使用脚本生成计算百分位数的值 |
脚本支持参数化输入,就像任何其他脚本一样 |
这会将script
参数解释为inline
带有painless
脚本语言且没有脚本参数的脚本。要使用存储的脚本,请使用以下语法:
GET latency/_search
{
"size": 0,
"aggs" : {
"load_time_outlier" : {
"percentiles" : {
"script" : {
"id": "my_script",
"params": {
"field": "load_time"
}
}
}
}
}
}
有许多不同的算法来计算百分位数。简单的实现只是将所有值存储在一个排序数组中。要找到第 50 个百分位数,您只需找到位于my_array[count(my_array) * 0.5]
的值。
显然,简单的实现无法扩展——排序后的数组随着数据集中值的数量线性增长。为了计算 Elasticsearch 集群中潜在的数十亿个值的 百分位数,需要计算近似百分位数。
percentile
指标使用的算法称为 TDigest(由 Ted Dunning 在 Digests 中引入 )。
使用此指标时,需要牢记一些准则:
- 精度与
q(1-q)
成正比。这意味着极端百分位数(例如 99%)比不太极端的百分位数(例如中位数)更准确 - 对于一小组值,百分位数是高度准确的(如果数据足够小,则可能 100% 准确)。
- 随着桶中值的数量增加,算法开始近似百分位数。它有效地保证节省内存和交易准确性。准确程度难以概括,因为这取决于数据分布和聚合数据量
下图显示了均匀分布的相对误差,具体取决于收集值的数量和请求的百分位数:
它显示了极端百分位数的精度如何更好。大量值的误差减少的原因是大数定律使值的分布越来越均匀,t-digest树可以更好地总结它。对于更偏斜的分布,情况就不是这样了。
警告
百分位聚合也是 。这意味着可以使用相同的数据获得略有不同的结果。
近似算法必须平衡内存利用率和估计精度。可以使用compression
参数控制此平衡:
GET latency/_search
{
"size": 0,
"aggs" : {
"load_time_outlier" : {
"percentiles" : {
"field" : "load_time",
"tdigest": {
"compression" : 200
}
}
}
}
}
TDigest 算法使用多个“节点”来近似百分位数——可用节点越多,与数据量成正比的准确性(和大内存占用)就越高。该compression
参数将最大节点数为20 * compression
。
因此,通过增加压缩值,可以以更多内存为代价来提高百分位数的准确性。较大的压缩值也会使算法变慢,因为底层树数据结构的大小会增加,从而导致操作成本更高。默认压缩值为 100
.
“节点”使用大约 32 字节的内存,因此在最坏的情况下(大量数据按顺序到达),默认设置将产生大约 KB 大小的 TDigest。在实践中,数据往往更随机,TDigest 将使用更少的内存。
NOTE
此设置公开了 HDR Histogram 的内部实现,并且将来可能会更改语法。
(高动态范围直方图)是一种替代实现,在计算延迟测量的百分位数时非常有用,因为它可以比 t-digest 实现更快,但需要更大的内存占用。此实现保持固定的最坏情况百分比错误(指定为有效数字的数量)。这意味着,如果在设置为 3 位有效数字的直方图中记录了从 1 微秒到 1 小时(3,600,000,000 微秒)的值,那么对于高达 1 毫秒和 3.6 秒(或更好)的值,它将保持 1 微秒的值分辨率) 为最大跟踪值(1 小时)。
可以通过method
在请求中指定参数来使用 HDR Histogram :
GET latency/_search
{
"size": 0,
"aggs" : {
"load_time_outlier" : {
"percentiles" : {
"field" : "load_time",
"percents" : [95, 99, 99.9],
"hdr": {
"number_of_significant_value_digits" : 3
}
}
}
}
}
hdr object 表示应该使用 HDR Histogram 来计算百分位数,并且可以在对象内部指定此算法的特定设置 |
number_of_significant_value_digits 以有效位数指定直方图值的分辨率 |
HDRHistogram 仅支持正值,如果传递负值则会出错。如果值的范围未知,则使用 HDRHistogram 也不是一个好主意,因为这可能会导致高内存使用率。
该missing
参数定义应如何处理缺少值的文档。默认情况下,它们将被忽略,但也可以将它们视为具有值。
GET latency/_search
{
"size": 0,
"aggs" : {
"grade_percentiles" : {
"percentiles" : {
"field" : "grade",
"missing": 10
}
}
}
}
| 该grade 字段中没有值的文档将与该值为10 的文档落入同一个桶中。 |