Skip to main content

百分比聚合 percentiles

Percentiles Aggregation

多值度量聚合,计算从聚合文档中提取的数值的一个或多个百分位数。这些值可以从文档中的特定数字字段中提取,也可以由提供的脚本生成。

百分位数显示观察值的某个百分比出现的点。例如,第95百分位是大于观察值的95%的值。

百分比通常用于查找异常值。在正态分布中,0.13和99.87个百分位数代表与平均值的三个标准差。任何超出三个标准偏差的数据通常被视为异常。

当检索到一个百分位数范围时,可以使用它们来估计数据分布,并确定数据是否倾斜、双峰等。

假设您的数据包含网站加载时间。平均加载时间和中值加载时间对管理员来说并不太有用,最大值可能更有趣。

GET latency/_search
{
"size": 0,
"aggs" : {
"load_time_outlier" : {
"percentiles" : {
"field" : "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之间的值):

GET latency/_search
{
"size": 0,
"aggs" : {
"load_time_outlier" : {
"percentiles" : {
"field" : "load_time",
"percents" : [95, 99, 99.9]
}
}
}
}

使用percents参数指定要计算的特定百分比

Keyed Response

默认情况下,keyed标志设置为true,它将一个唯一的字符串键与每个bucket相关联,并将范围作为哈希而不是数组返回。将键控标志设置为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
}
]
}
}
}

Script

百分比度量支持脚本。例如,如果我们的加载时间以毫秒为单位,但我们希望百分位数以秒为单位计算,那么我们可以使用脚本动态转换它们:

GET latency/_search
{
"size": 0,
"aggs" : {
"load_time_outlier" : {
"percentiles" : {
"script" : {
"lang": "painless",
"source": "doc['load_time'].value / params.timeUnit",
"params" : {
"timeUnit" : 1000
}
}
}
}
}
}
  1. field参数替换为脚本参数,该参数使用脚本生成计算百分位数的值
  2. 与任何其他脚本一样,脚本支持参数化输入

这将把脚本参数解释为具有painless script且没有脚本参数的inline script。要使用存储的脚本,请使用以下语法:

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集群中潜在数十亿值的百分位数,需要计算近似百分位数。

百分位数度量所使用的算法称为TDigest(由Ted Dunning在使用T位数计算精确分位数中介绍)。

在使用此度量时,需要记住以下几条准则:

精度与q(1-q)成正比。这意味着极端百分位数(例如99%)比不太极端的百分位数更准确,例如中值

对于小的值集,百分位数是高度准确的(如果数据足够小,则可能100%准确)。

作为a b中的值的数量

它显示了极端百分位数的精度如何更好。对于大量值,误差减小的原因是,大数定律使值的分布越来越均匀,t-digest tree可以更好地总结它。对于更为倾斜的分布,情况并非如此。

百分比聚合也是非确定性的。这意味着使用相同的数据可以得到稍微不同的结果。

压缩

近似算法必须平衡内存利用率和估计精度。可以使用压缩参数控制该平衡:

GET latency/_search
{
"size": 0,
"aggs" : {
"load_time_outlier" : {
"percentiles" : {
"field" : "load_time",
"tdigest": {
"compression" : 200
}
}
}
}
}

Compression 控制内存使用和近似错误

TDigest算法使用许多“节点”来逼近百分位数 — 可用节点越多,与数据量成比例的准确性(和大内存占用)就越高。压缩参数将最大节点数限制为20*压缩。

因此,通过增加压缩值,可以以更多内存为代价提高百分位数的准确性。更大的压缩值也会使算法变慢,因为底层树数据结构的大小会增加,从而导致更昂贵的操作。默认压缩值为100。

一个“节点”大约使用32字节的内存,因此在最坏情况下(大量数据按顺序到达),默认设置将产生一个大约64KB大小的TDigest。实际上,数据往往更随机,TDigest将使用更少的内存。

HDR直方图

HDR直方图(High Dynamic Range Histogram,高动态范围直方图)是一种替代实现,在计算延迟测量的百分位数时非常有用,因为它可以比t摘要实现更快,但需要更大的内存占用。此实现保持固定的最坏情况百分比错误(指定为有效位数)。这意味着,如果在直方图中以1微秒到1小时(360000000微秒)的值记录数据,并将其设置为3个有效数字,则对于最长1毫秒的值,将保持1微秒的值分辨率,对于最大跟踪值(1小时),将保持3.6秒(或更高)的值分辨率。

通过在请求中指定方法参数,可以使用HDR直方图:

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
}
}
}
}
}
  1. hdr对象表示应该使用hdr直方图来计算百分位数,并且可以在对象内指定此算法的特定设置

  2. number_of_significant_value_digits指定直方图值的分辨率(有效位数)

HDR Histogram仅支持正值,如果传递负值,则会出错。如果值的范围未知,则使用HDR Histogram也不是一个好主意,因为这可能会导致高内存使用率。

Missing Value

缺少的参数定义了应如何处理缺少值的文档。默认情况下,它们将被忽略,但也可以将它们视为具有值。

GET latency/_search
{
"size": 0,
"aggs" : {
"grade_percentiles" : {
"percentiles" : {
"field" : "grade",
"missing": 10
}
}
}
}

grade字段中没有值的文档将与值为10的文档属于同一个存储桶。