跳到主要内容

关键词聚合 Terms

Terms Aggregation

基于多桶聚合,按字段值分桶聚合。比如性别有男、女,就会创建两个桶,分别存放男女的信息。默认会搜集doc_count的信息,即记录有多少男生,有多少女生,然后返回给客户端,这样就完成了一个terms统计。

信息

terms agg 通常用于keyword字段,如果想运用在text字段,您需要启用fielddata 。

一个简单的示例

GET /_search
{
"aggs" : {
"genres" : {
"terms" : { "field" : "genre" }
}
}
}

返回

{
...
"aggregations" : {
"genres" : {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets" : [
{
"key" : "electronic",
"doc_count" : 6
},
{
"key" : "rock",
"doc_count" : 3
},
{
"key" : "jazz",
"doc_count" : 2
}
]
}
}
}

Size

默认情况下,terms agg将返回doc_count排序的前十个term的桶。可以通过设置size参数来更改此默认行为。

Shard Size

请求的size越大,结果就越准确,但计算最终结果的成本也越高(这两方面都是由于在shard级别上管理的优先级队列越大,以及节点和客户端之间的数据传输越大)。

可以通过shard_size参数设置协调节点向各个分片请求的term个数,然后在协调节点进行聚合,最后只返回size个term给到客户端,shard_size >= size,如果shard_size设置小于size,ES会自动将其设置为size,默认情况下shard_size建议设置为(1.5 * size + 10)。

Order

通过设置order参数,可以自定义桶的顺序。默认情况下,存储桶按其doc_count降序排列。

  • 按桶的doc_count升序排序:
GET /_search
{
"aggs" : {
"genres" : {
"terms" : {
"field" : "genre",
"order" : { "_count" : "asc" }
}
}
}
}
  • 按term的字母顺序升序排列存储桶:
GET /_search
{
"aggs" : {
"genres" : {
"terms" : {
"field" : "genre",
"order" : { "_key" : "asc" }
}
}
}
}
  • 按单值度量子聚合对桶进行排序:
GET /_search
{
"aggs" : {
"genres" : {
"terms" : {
"field" : "genre",
"order" : { "max_play_count" : "desc" }
},
"aggs" : {
"max_play_count" : { "max" : { "field" : "play_count" } }
}
}
}
}
  • 按多值度量子聚合对桶进行排序:
GET /_search
{
"aggs" : {
"genres" : {
"terms" : {
"field" : "genre",
"order" : { "playback_stats.max" : "desc" }
},
"aggs" : {
"playback_stats" : { "stats" : { "field" : "play_count" } }
}
}
}
}
  • 根据层次结构中的“deeper”聚合对桶进行排序。

path必须按以下格式定义

AGG_SEPARATOR       =  '>' ;
METRIC_SEPARATOR = '.' ;
AGG_NAME = <the name of the aggregation> ;
METRIC = <the name of the metric (in case of multi-value metrics aggregation)> ;
PATH = <AGG_NAME> [ <AGG_SEPARATOR>, <AGG_NAME> ]* [ <METRIC_SEPARATOR>, <METRIC> ] ;
GET /_search
{
"aggs" : {
"countries" : {
"terms" : {
"field" : "artist.country",
"order" : { "rock>playback_stats.avg" : "desc" }
},
"aggs" : {
"rock" : {
"filter" : { "term" : { "genre" : "rock" }},
"aggs" : {
"playback_stats" : { "stats" : { "field" : "play_count" }}
}
}
}
}
}
}

以上将根据摇滚歌曲中的平均播放次数对艺术家的国家进行排序。

通过提供一系列订购标准,可以使用多个标准来订购存储桶,如下所示:

GET /_search
{
"aggs" : {
"countries" : {
"terms" : {
"field" : "artist.country",
"order" : [ { "rock>playback_stats.avg" : "desc" }, { "_count" : "desc" } ]
},
"aggs" : {
"rock" : {
"filter" : { "term" : { "genre" : "rock" }},
"aggs" : {
"playback_stats" : { "stats" : { "field" : "play_count" }}
}
}
}
}
}
}

以上将根据摇滚歌曲中的平均播放次数,然后按其doc_count降序对艺术家的国家/地区进行排序。

Minimum document count

可以使用min_doc_count选项仅返回与配置的命中数匹配的项:

GET /_search
{
"aggs" : {
"tags" : {
"terms" : {
"field" : "tags",
"min_doc_count": 10
}
}
}
}

上述聚合将仅返回在10次或更多次命中中找到的标记。默认值为1。

Script

GET /_search
{
"aggs" : {
"genres" : {
"terms" : {
"script" : {
"source": "doc['genre'].value",
"lang": "painless"
}
}
}
}
}

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

GET /_search
{
"aggs" : {
"genres" : {
"terms" : {
"script" : {
"id": "my_script",
"params": {
"field": "genre"
}
}
}
}
}
}

Value Script

GET /_search
{
"aggs" : {
"genres" : {
"terms" : {
"field" : "genre",
"script" : {
"source" : "'Genre: ' +_value",
"lang" : "painless"
}
}
}
}
}

Filtering Values

可以过滤将为其创建桶的值。这可以使用基于正则表达式字符串或精确值数组的include和exclude参数来完成。此外,include子句可以使用分区表达式进行筛选。

Filtering Values 使用正则表达式

GET /_search
{
"aggs" : {
"tags" : {
"terms" : {
"field" : "tags",
"include" : ".*sport.*",
"exclude" : "water_.*"
}
}
}
}

在上面的示例中,将为所有包含单词sport的标记创建bucket,但以water_开头的标记除外(因此不会聚合标记watersports)。include正则表达式将确定“允许”聚合的值,而exclude则确定不应聚合的值。当两者都被定义时,exclude具有优先权,这意味着首先计算include,然后才计算exclude。

Filtering Values with exact values

对于基于精确值的匹配,include和exclude参数可以简单地采用一个字符串数组来表示索引中的term:

GET /_search
{
"aggs" : {
"JapaneseCars" : {
"terms" : {
"field" : "make",
"include" : ["mazda", "honda"]
}
},
"ActiveCarManufacturers" : {
"terms" : {
"field" : "make",
"exclude" : ["rover", "jensen"]
}
}
}
}

Filtering Values with partitions

有时,在单个请求/响应对中处理的唯一term太多,因此将分析分解为多个请求可能很有用。这可以通过在查询时将字段的值分组为多个分区并在每个请求中只处理一个分区来实现。考虑此请求,该请求正在查找最近未记录任何访问权限的帐户

GET /_search
{
"size": 0,
"aggs": {
"expired_sessions": {
"terms": {
"field": "account_id",
"include": {
"partition": 0,
"num_partitions": 20
},
"size": 10000,
"order": {
"last_access": "asc"
}
},
"aggs": {
"last_access": {
"max": {
"field": "access_date"
}
}
}
}
}
}

此请求正在查找客户帐户子集的上次登录访问日期,因为我们可能希望终止一些很久没有看到的客户帐户。num_partitions设置要求将唯一的account_id平均组织为二十个分区(0到19)。并且此请求中的分区设置过滤为仅考虑落入分区0的account_id。后续请求应请求分区1,然后请求分区2等以完成过期帐户分析。

注意,返回结果数的大小设置需要使用num_partitions进行调整。对于这个特定的帐户过期示例,平衡size和num_partitions值的过程如下:

  • 使用基数聚合来估计唯一account_id值的总数
  • 为num_partitions选择一个值,将数字从1)拆分为更易于管理的块
  • 为每个分区的响应数量选择一个大小值
  • 运行测试请求

如果出现断路器错误,我们试图在一个请求中做太多,必须增加num_partitions。如果请求成功,但按日期排序的测试响应中的最后一个帐户ID仍然是我们可能希望过期的帐户,那么我们可能缺少感兴趣的帐户,并且将我们的数字设置得太低。我们必须

  • 增加size参数以每个分区返回更多结果(可能会占用大量内存)或
  • 增加num_partitions以减少每个请求的帐户数(可能会增加总体处理时间,因为我们需要发出更多请求)

最终,这是在管理处理单个请求所需的Elasticsearch资源和客户端应用程序完成任务所必须发出的请求量之间的平衡。

多字段 terms aggregation

term aggregation不支持从同一文档中的多个字段收集term。原因是term agg本身不收集字符串term值,而是使用全局序数global ordinals生成字段中所有唯一值的列表。全局序数会导致重要的性能提升,这在多个字段中是不可能的。

有两种方法可用于跨多个字段执行term agg:

  • Script 使用脚本从多个字段检索term。这将禁用全局序数优化,并将比从单个字段中收集term慢,但它为您提供了在搜索时实现此选项的灵活性
  • copy_to field 如果提前知道要从两个或多个字段中收集term,那么在映射中使用copy_to在索引时创建一个新的专用字段,其中包含两个字段的值。您可以在这个字段上进行聚合,这将受益于全局序数优化。

收集模式

推迟子聚合的计算

对于具有许多唯一项和少量所需结果的字段,将子聚合的计算延迟到顶级父级标记被删除之前可能会更有效。通常,聚合树的所有分支在一次深度优先过程中展开,然后才进行任何修剪。在某些情况下,这可能非常浪费,并会影响内存限制。一个示例问题场景是查询电影数据库,查找10位最受欢迎的演员及其5位最常见的合作主演:

GET /_search
{
"aggs" : {
"actors" : {
"terms" : {
"field" : "actors",
"size" : 10
},
"aggs" : {
"costars" : {
"terms" : {
"field" : "actors",
"size" : 5
}
}
}
}
}
}

尽管参与者的数量可能相对较少,我们只需要50个结果桶,但在计算过程中,桶的组合爆炸-单个参与者可以产生n²个桶,其中n是参与者的数量。明智的选择是首先确定10位最受欢迎的演员,然后再检查这10位演员的最佳合作演员。这种替代策略是我们所称的breadth_first收集模式,而不是depth_firs模式。

GET /_search
{
"aggs" : {
"actors" : {
"terms" : {
"field" : "actors",
"size" : 10,
"collect_mode" : "breadth_first"
},
"aggs" : {
"costars" : {
"terms" : {
"field" : "actors",
"size" : 5
}
}
}
}
}
}

当使用breadth_first模式时,属于最高存储桶的一组文档将被缓存以供后续回放,因此这样做会产生内存开销,这与匹配文档的数量成线性关系。请注意,在使用breadth_first设置时,order参数仍然可以用于引用子聚合中的数据-父聚合知道需要在调用任何其他子聚合之前先调用此子聚合。

Execution hint

执行term聚合有不同的机制:

  • 通过直接使用字段值来聚合每个存储桶(map)的数据
  • 通过使用字段的全局序数并为每个全局序数分配一个桶(global_ordinals)

Elasticsearch尝试使用合理的默认值,因此这通常不需要配置。

globalordinals是关键字字段的默认选项,它使用全局ordinals动态分配桶,因此内存使用量与聚合范围内的文档值的数量成线性关系。

只有当很少文档与查询匹配时,才应考虑map。否则,基于序数的执行模式明显更快。默认情况下,map仅在对脚本运行聚合时使用,因为它们没有序数。

GET /_search
{
"aggs" : {
"tags" : {
"terms" : {
"field" : "tags",
"execution_hint": "map"
}
}
}
}