工程

所有内容皆可聚合:Elasticsearch 7 中的新聚合

自 1.0 版本以来,聚合框架一直是 Elasticsearch 的重要组成部分,多年来已进行多次优化、修复,甚至是一些大修。自从 Elasticsearch 7.0 发布以来,Elasticsearch 中已添加了相当多的新聚合,例如 rare_termstop_metricsauto_date_histogram 聚合。在本篇博文中,我们将探究其中的一些聚合,仔细看看这些聚合能为您做些什么。

为了测试这些新的聚合,我们需要在 Elasticsearch 7.9 部署中设置一个示例数据集。要想确保自己拥有所有最新功能(以及修补程序),让集群保持最新版本是一个不错的方式;但如果您用的版本较旧,而且升级也不是可取的办法,则可以通过 Elastic Cloud 的免费试用版来实现这一目的。

下面的文档可代表一个电子商务用例,在这个用例中,用户点击一款产品并检索产品详细信息。当然,这当中缺少许多细节,例如单个用户的会话 ID。后续您可以启动和监测会话,甚至可以更进一步,利用转换来深入洞察您的数据。本篇博文力求简单明了,确保所有概念都能讲明白。

在 Kibana 中使用 Discover 或从命令行中使用 cURL 来索引示例数据: 

PUT website-analytics/_bulk?refresh 
{ "index" : {}} 
{ "product_id":"123", "@timestamp" :"2020-10-01T11:11:23.000Z", "price" :12.34, "response_time_ms":242 } 
{ "index" : {}} 
{ "product_id":"456", "@timestamp" :"2020-10-02T12:14:00.000Z", "price" :20.58, "response_time_ms":98 } 
{ "index" : {}} 
{ "product_id":"789", "@timestamp" :"2020-10-03T13:15:00.000Z", "price" :34.16, "response_time_ms":123 } 
{ "index" : {}} 
{ "product_id":"123", "@timestamp" :"2020-10-02T14:16:00.000Z", "price" :12.34, "response_time_ms":465 } 
{ "index" : {}} 
{ "product_id":"123", "@timestamp" :"2020-10-02T14:18:00.000Z", "price" :12.34, "response_time_ms":158 } 
{ "index" : {}} 
{ "product_id":"123", "@timestamp" :"2020-10-03T15:17:00.000Z", "price" :12.34, "response_time_ms":168 } 
{ "index" : {}} 
{ "product_id":"789", "@timestamp" :"2020-10-06T15:17:00.000Z", "price" :34.16, "response_time_ms":220 } 
{ "index" : {}} 
{ "product_id":"789", "@timestamp" :"2020-10-10T15:17:00.000Z", "price" :34.16, "response_time_ms":99 }

Auto-bucketing 聚合

这些类型的聚合改变了定义存储桶的方式。当您在使用基于时间的聚合时,通常会根据时间间隔(例如 1d)来定义存储桶。但有时您并不知道数据的性质,从用户的角度来看,只告诉预期的存储桶数会更容易一些。

这就是下面两个新聚合发挥作用的地方。

auto_date_histogram 聚合

auto_date_histogram 聚合在日期字段上运行,允许您配置期望返回的存储桶数。下面我们在小型数据集上尝试一下:

POST website-analytics/_search?size=0 
{ 
  "aggs": { 
    "views_over_time": { 
      "auto_date_histogram": { 
        "field": "@timestamp",
        "buckets":3 
      } 
    } 
  } 
} 
POST website-analytics/_search?size=0 
{ 
  "aggs": { 
    "views_over_time": { 
      "auto_date_histogram": { 
        "field": "@timestamp",
        "buckets":10 
      } 
    } 
  } 
}

通过运行这两个查询会看到,返回的 interval 是根据所请求的存储桶数确定的。如果请求的是 3 个存储桶,则应为每周 1 个存储桶,而如果请求的是 10 个存储桶,则应为每天 1 个存储桶。

如果需要最小时间间隔,也可以这样配置,详情可参见 auto_date_histogram 文档

variable_width_histogram 聚合

通过可变宽度直方图,可按照预配置的数量动态创建存储桶。此外,与常规直方图聚合的固定宽度相比,这些存储桶的宽度是可变的。

POST website-analytics/_search?size=0 
{ 
  "aggs": { 
    "prices": { 
      "variable_width_histogram": { 
        "field": "price",
        "buckets":3 
      } 
    } 
  } 
}

由于我们的数据集中只有三种不同价格,因此最小值/最大值/键值都是相同的。但是,您可以尝试使用两个存储桶,就会看到一个存储桶现在具有不同的值。

另外,请记住,存储桶边界都是近似值

在电子商务应用程序中可能就有这方面的用例,例如,您希望在分面导航中显示价格存储桶。但是,使用这个选项会让您的网站导航对异常值相当敏感,因此请在执行此操作之前最好设置一个类别筛选器。

感谢社区成员 James 将这个聚合引入 Elasticsearch,并对分层凝聚聚类算法进行了 Grok 解析。有关更多详情,请参阅 GitHub 拉取请求

字符串聚合

以下聚合适用于基于字符串的字段,通常为 keyword 字段。

rare_terms 聚合

作为 Elastic Stack 用户,您可能或多或少对 terms 聚合已有所了解。这个聚合将返回数据集中出现次数最多的词。您也可以更改排序方式来返回出现次数最少的词。但是,这会产生一个无界误差,因此结果可能是一个近似值,因为这些数据是通过集群中的几个分片收集而来的。这是因为 Elasticsearch 会尝试阻止从不同分片将所有数据复制到一个节点,因为这样做既昂贵又缓慢。

rare_terms 聚合会尝试通过使用与 terms 聚合不同的实施过程来规避这些问题。尽管这仍是在进行一个近似计数,但 rare_terms 聚合具有一个定义明确的有界误差。

要找出在上述数据集中索引最少的产品 ID,请尝试以下操作

POST website-analytics/_search?size=0 
{ 
  "aggs": { 
    "rarest_product_ids": { 
      "rare_terms": { 
        "field": "product_id.keyword" 
      } 
    } 
  } 
}

此外,您还可以使用 max_doc_count(与 terms 聚合的 min_doc_count 相反),并更改要返回的存储桶数。

string_stats 聚合

如何获取有关数据中字符串字段值的一些统计信息呢?下面让我们来试试 string_stats 聚合

POST website-analytics/_search?size=0 
{ 
  "aggs": { 
    "rarest_product_ids": { 
      "string_stats": { 
        "field": "product_id.keyword",
        "show_distribution" : true 
      } 
    } 
  } 
}

这将返回有关该字段中字符串的最小/最大/平均长度的统计信息,但是通过添加 show_distribution 参数,您还可看到所找到的每个字符的分布。

这是一种快速检查数据来发现异常值的简便方法,这些异常值可能是错误索引的数据,例如,产品 ID 过长或过短。此外,返回的香农熵值还可用于发现 DNS 数据渗透尝试之类的目的。

基于指标的聚合

接下来我们深入了解一下第二组聚合,即在分桶的数字字段上进行计算的聚合。

top_metrics 聚合

您可能对 top_hits 聚合已有所了解,这个聚合会返回完整的命中结果(包括命中源)。但是,如果您只想查看单个值,并希望以此排序,请查看 top_metrics 聚合。如果您不需要整个文档,那么这个聚合将比 top_hits 聚合快很多,通常用于从每个存储桶中检索最新值。

在我们的点击流数据集中,您可能会对最新点击事件的价格感兴趣。

POST website-analytics/_search 
{ 
  "size":0,
  "aggs": { 
    "tm": { 
      "top_metrics": { 
        "metrics": {"field": "price"},
        "sort": {"@timestamp": "desc"} 
      } 
    } 
  } 
}

这里还支持按 _score 或地理距离排序。另外,您还可以指定多个指标,这样您可以向 metrics 字段添加另一个字段,然后需要将其变为数组。

boxplot 聚合

boxplot 聚合正如其名称所示,提供箱形图

GET website-analytics/_search 
{ 
  "size":0,
  "aggs": { 
    "by_date": { 
      "date_histogram": { 
        "field": "@timestamp",
        "calendar_interval": "day",
        "min_doc_count":1 
      },
      "aggs": { 
        "load_time_boxplot": { 
          "boxplot": { 
            "field": "price" 
          } 
        } 
      } 
    } 
  } 
}

上面的查询会返回每天的箱形图,其中每日存储桶中存有数据。

我们将跳过 t-test 聚合,因为此处的超小型数据集无法执行任何有用的聚合请求。要查看这个聚合的值,您需要有一个数据集,在其中假设可以通过统计假设发现的行为变化。

管道聚合

接下来是在聚合之上的聚合,称为管道聚合。去年添加了很多这类聚合。

cumulative_cardinality 聚合

这个聚合对于查找数据集中新项目的数量非常有用。

GET website-analytics/_search 
{ 
  "size":0,
  "aggs": { 
    "by_day": { 
      "date_histogram": { 
        "field": "@timestamp",
        "calendar_interval": "day" 
      },
      "aggs": { 
        "distinct_products": { 
          "cardinality": { 
            "field": "product_id.keyword" 
          } 
        },
        "total_new_products": { 
          "cumulative_cardinality": { 
            "buckets_path": "distinct_products" 
          } 
        } 
      } 
    } 
  } 
}

通过上面的查询,您可以计算出每天有多少新产品和之前不知名产品被访问过,并创建这些产品的计数。在电子商务环境中,这可能有助于您查明新产品是否得到了真正关注,或者畅销产品是否位居榜首,如果不是,您也许应该改变一下营销方式了。

标准化聚合

下面我们大致了解一下,按百分比来计算,哪一天的流量最大,其中 100% 是指与查询对应的全部数据。

GET website-analytics/_search 
{ 
  "size":0,
  "aggs": { 
    "by_day": { 
      "date_histogram": { 
        "field": "@timestamp",
        "calendar_interval": "day" 
      },
      "aggs": { 
        "normalize": { 
          "normalize": { 
            "buckets_path": "_count",
            "method": "percent_of_sum" 
          } 
        } 
      } 
    } 
  } 
}

这将返回每个存储桶的附加信息:每个存储桶中找到的文档数与搜索返回的总文档数的百分比。

您可能需要查看标准化聚合文档,因为可以从中选择更多的 method 值,例如,均值或某个范围内的重新定标。

moving percentiles 聚合

这个管道聚合在 percentiles 聚合之上运行,并使用滑动窗口计算累积百分位数。

GET website-analytics/_search 
{ 
  "size":0,
  "aggs": { 
    "by_day": { 
      "date_histogram": { 
        "field": "@timestamp",
        "calendar_interval": "day" 
      },
      "aggs": { 
        "response_time": { 
          "percentiles": { 
            "field": "response_time_ms",
            "percents": [ 75, 99 ] 
          } 
        },
        "moving_pct": { 
          "moving_percentiles": { 
            "buckets_path": "response_time",
            "window":2 
          } 
        } 
      } 
    } 
  } 
}

这里需要展开讲一下,我们继续吧。在按天进行存储后,percentiles 聚合将计算每天存储桶的百分位数。然后,moving_percentiles 管道聚合取前两个存储桶并从中计算出移动平均值。请注意,如果您还想包含当前的存储桶,则可以通过使用 shift 参数来更改用于计算的存储桶的行为。

pipeline inference 聚合

我们将跳过 inference 存储桶聚合,因为计划会很快发布一篇博文来专门解释这个聚合。先满足一下大家的好奇心:您可以针对父级存储桶聚合的结果运行经过预训练的模型。敬请关注!

支持 histogram 字段类型

这不是一个严格意义上的聚合,但聚合会受到这种数据类型的影响,因此特意在此提一下。

您可能已经或多或少接触过 histogram 字段类型,它允许您存储预聚合的数值数据,例如,该数据在 Elastic 可观测性中就得到了广泛的使用。这一特殊字段类型支持聚合的子集,当然,您也会发现一些尚不受支持的聚合。为了支持这些聚合,还有大量工作需要去做。

在地理位置聚合中支持 geo_shape

同样,这也不是严格意义上的单个聚合,但这是一个巨大的进步,因此也有必要提一下。 

除了 geo_point 字段类型之外,我们还投巨资以使 geo_boundsgeo_tilegeo_hashgridgeo_centroid 聚合能与 geo_shape 字段类型一起使用。

更多聚合,敬请期待

感谢您的关注。如果您对这些聚合还有其他疑问,请在我们的讨论论坛中继续提问。此外,在即将发布的版本中,我们还会计划添加更多的聚合。其中一个就是 rate 聚合,它可以在 date_histogram 聚合中使用,并计算每个存储桶中文档或字段的比率。

如果您想尝试这些聚合,可在 Elastic Cloud 的免费试用版中快速部署一个集群,然后使用自己的数据自由操作!