ElasticSearch学习笔记之二十五 索引词聚合

Terms Aggregation(索引词聚合)

Terms Aggregation是一个动态的多分组聚合,每个分组都有一个独一无二的索引词。
例如:

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
                }
            ]
        }
    }
}

doc_count_error_upper_bound代表每个索引词文档计数的错误上限。
当存在大量不同的索引词,ElasticSearch只返回排名靠前的索引词分组,sum_other_doc_count代表所有没有放在响应分组的文档计数之和。

默认情况下,索引词通过文档计数(doc_count)排序,并且会返回排名前十的索引词分组,我们可以通过size参数对返回结果的数量进行修改。

Size

size可以用来定义在所有的索引词分组中需要返回的分组的数量. 默认情况下,协调搜索进程的节点会请求每个分片提供顶层与规模大小相同的索引词分组,当所有的分片响应完成是,节点会缩小最终结果的规模并返回给用户。

注意:
如果你希望在嵌套聚合中检索索引词和索引词的聚合,应该只用Composite aggregation ,该聚合允许在所有可能的索引词上进行分页 而不是设置一个大于 terms aggregation基数的size. terms aggregation意味者顶级聚合,不允许分页。

Document counts are approximate(文档计数是近似值)

正如上面所说的, terms aggregation的文档的计数 (或者任意子聚合的结果) 不总是精确的。这是因为每个分片提供自己的排序视图并将它们结合成一个最终视图。考虑到下面的情况:

下面的请求从一个具有3个分片的索引中获取product字段按照降序的前5个索引词,在这种情况下,每个分片会给出前5索引词。

GET /_search
{
    "aggs" : {
        "products" : {
            "terms" : {
                "field" : "product",
                "size" : 5
            }
        }
    }
}

三个分片中的索引语如下所示,括号内是各自的文件计数:

扫描二维码关注公众号,回复: 3948694 查看本文章
Shard A Shard B Shard C
1 Product A (25) Product A (30) Product A (45)
2 Product B (18) Product B (25) Product C (44)
3 Product C (6) Product F (17) Product Z (36)
4 Product D (3) Product Z (16) Product G (30)
5 Product E (2) Product G (15) Product E (29)
6 Product F (2) Product H (14) Product H (28)
7 Product G (2) Product I (10) Product Q (2)
8 Product H (2) Product Q (6) Product D (1)
9 Product I (1) Product J (8)
10 Product J (1) Product C (4)

分片会分辨返回它们前5索引词,所以结果如下:

Shard A Shard B Shard C
1 Product A (25) Product A (30)
2 Product B (18) Product B (25)
3 Product C (6) Product F (17)
4 Product D (3) Product Z (16)
5 Product E (2) Product G (15)

把3个分片的前5索引词组合成最终的前5索引词

排名 数量
1 Product A (100)
2 Product Z (52)
3 Product C (50)
4 Product G (45)
5 Product B (43)

因为产品A是从所有分片中返回的,所以我们知道它的文档计数值是准确的。产品C只被分片A和C返回,所以它的文档计数显示为50,但这不是一个精确的计数。产品C存在于分片B上,但其计数为4,不足以将产品C放入该分片的前5个列表中。产品Z也只返回2分片,但第三分片不包含这个词。在组合结果以产生最终的索引词列表时,Product C文档计数有错误而不是Product Z的。产品H在所有3个分片上有44个文档计数,但是没有包括在最终的术语列表中,因为它没有在任何分片上进入前5个索引词。

Shard Size

size属性设置的越大值越精确, 但是,计算最终结果也越昂贵(这是由于在分片级别上管理的优先级队列越大,也是由于节点和客户端之间的数据传输越大)

shard_size 属性可以用来最小化较大请求大小的额外工作。当定义时,它将决定协调节点将从每个分片请求多少个索引词。一旦所有分片都响应了,协调节点将根据大小参数将它们减少到最终结果——通过这种方式,可以提高返回的索引词的精确度,并避免将大量存储桶列表流回客户端的开销。

注意:
shard_size 不能比size小 (因为这样做没有意义). 当它比较小时elasticsearch会重写它和size相等

在单分片索引中shard_size将会和size一样大, 其他的情况下等于 (size * 1.5 + 10)

Calculating Document Count Error

terms aggregation有2个错误值 . 第一个给出聚合的整体值,该值表示未进入最终索引词列表的索引词的最大潜在文档计数。这是从从每个分片返回的最后一个索引词的文档计数的总和计算的。对于上面给出的例子,该值将是46(2 + 15 + 29)。这意味着,在最坏的情况下,没有返回的索引词可以具有第四的最高文档计数。

{
    ...
    "aggregations" : {
        "products" : {
            "doc_count_error_upper_bound" : 46,
            "sum_other_doc_count" : 79,
            "buckets" : [
                {
                    "key" : "Product A",
                    "doc_count" : 100
                },
                {
                    "key" : "Product Z",
                    "doc_count" : 52
                }
                ...
            ]
        }
    }
}

设置show_term_doc_count_error 参数为true将使第二个错误值生效:

GET /_search
{
    "aggs" : {
        "products" : {
            "terms" : {
                "field" : "product",
                "size" : 5,
                "show_term_doc_count_error": true
            }
        }
    }
}

这显示了聚合返回的每个项的错误值,该值表示文档计数中的最坏情况错误,它在决定shard_size参数的取值时会非常有用使用。这是通过汇总所有没有返回该项的分片返回的最后期限的文件计数来计算的。在上面的示例中,Product C的文档计数中的错误是15,因为Shard B是唯一不返回索引词的分片,并且它返回的最后一个索引词的文档计数是15。产品C的实际文档计数是54,所以文档计数实际上只减少了4,即使最糟糕的情况是在15之前。然而,产品A的文档计数有0错误,因为每个分片都返回它,所以我们可以确信返回的计数是准确的。

{
    ...
    "aggregations" : {
        "products" : {
            "doc_count_error_upper_bound" : 46,
            "sum_other_doc_count" : 79,
            "buckets" : [
                {
                    "key" : "Product A",
                    "doc_count" : 100,
                    "doc_count_error_upper_bound" : 0
                },
                {
                    "key" : "Product Z",
                    "doc_count" : 52,
                    "doc_count_error_upper_bound" : 2
                }
                ...
            ]
        }
    }
}

这些误差只能通过降序的文档计数来排序。当聚合由项值本身(升序或降序)排序时,文档计数中没有错误,因为如果一个碎片没有返回出现在来自另一个碎片的结果中的特定项,那么它的索引中必须没有该项。当按照子聚合或按递增文档计数的顺序对聚合进行排序时,无法确定文档计数中的错误,并且给出-1值来指示这一点。

Order

通过设置 order 参数可以自定义分组的排序,默认情况下,分组会按照文档计数降序排序。我们也可以改为下面这种

警告:
通过升序计数或子聚合进行排序是不鼓励的,因为它会增加文档计数的错误。当查询单分片索引时,或者当聚集的字段被用作索引时间的routing key时,这是没有问题的:在这些情况下,结果将是准确的,因为分片具有不相交的值。然后,相反的情况下,错误是无法避免的。一个可能仍然有用的特殊情况是通过MIN或MAX聚合进行排序:计数不准确,但至少正确选择顶部分组。

根据文档的 _count 递增排序:

GET /_search
{
    "aggs" : {
        "genres" : {
            "terms" : {
                "field" : "genre",
                "order" : { "_count" : "asc" }
            }
        }
    }
}

根据索引词字母顺序递增排序:

GET /_search
{
    "aggs" : {
        "genres" : {
            "terms" : {
                "field" : "genre",
                "order" : { "_key" : "asc" }
            }
        }
    }
}

警告
elasticsearch6之后弃用
_key而不是 _term 去排序索引词分组

通过单值指标子聚合排序(通过聚合名称定义):

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" } }
            }
        }
    }
}

注意
Pipeline aggs 不可以用于排序。
Pipeline aggregations 在所有其他聚合完成之后,在计算阶段运行。由于这个原因,它们不可以用于排序

无论聚合路径上的最后一个聚合是单分组还是多分组,只要聚合路径是一个单分组类型,就可以基于更深的一层聚合惊醒分组排序。单分组类型排序会继续分组里面的文档的数字进行(例如doc_count),多组类型应用相同的规则(多组指标聚合,路径必须指明用来排序的指标名称;如果是单组指标聚合,得到的值将用于排序)
路径的定义方式如下:

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个或更多点击中找到的hints。默认值为1。

索引词被收集并排序在分片级别,并与第二步骤中从其他分片收集的索引合并。但是,分片没有关于可用的全局文档计数的信息。是否将一个项添加到候选列表的决定仅取决于使用本地分片频率对碎分计算的顺序。仅在合并所有分片的局部项统计量后才应用MixDoc计数准则。在某种程度上,将索引词作为候选项添加的决定是在不确定索引词是否真正达到所需的min_doc_count的情况下作出的。如果低频项填充到候选列表中,这可能导致许多(全球)高频项在最终结果中丢失。为了避免这一点,可以增加shard_size参数,以允许碎片上更多的候选项。然而,这增加了内存消耗和网络流量。

shard_min_doc_count 参数

shard_min_doc_count参数用于调节如果索引词实际上应该被添加到候选列表或者不应该被添加到min_doc_count中的分片所具有的确定性。如果集合中的局部碎片频率高于shard_min_doc_count计数,则仅考虑索引词。如果您的文档包含许多低频索引词,并且您对这些索引词不感兴趣(例如,拼写错误),那么您可以设置shard_min_doc_count参数以筛选分片级别上的候选索引词,即使经过合理的确定也不会达到所需的min_doc_count。合并本地计数。默认情况下,shard_min_doc_count设置为0,除非显式设置,否则不会产生任何影响。

注意
设置shard_min_doc_count=0也将返回与不匹配的条件的分组。然而,一些返回的文档计数为零的索引词可能只属于从其他类型删除的文档或文档,因此没有保证._all查询将找到这些索引词的正文档计数。

警告
当不是用文档计数降序排序时,min_doc_count取值太大将使返回一些小于大小的分组,因为没有从分片收集足够的数据。丢失分组可以通过增加shard_size来恢复。设置shard_min_doc_count值太高会导致在分片级别上过滤掉索引词。这个值应该比min_doc_count/#shards低很多。

Script

用script获取索引词:

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

这将将脚本参数解释为具有默认脚本语言和脚本参数的内联脚本。使用存储的脚本使用以下语法:

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

我们可以利用includeexclude字段对创建分组的索引词进行过滤,这个过程基于整个表达式或者一组精确值。此外,include子句可以使用分区表达式进行筛选。

Filtering Values with regular expressions

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

上面的例子中,分组会包含所有tags字段包含sport并且不是water开头的(所以water_sports不会被聚合)。include子句将决定哪些值允许被聚合, exclude子句决定哪些值不会被聚合. 当同时定义的时候exclude具有优先权,这也意味者,首先评估include,然后才exclude
语法和正则表达式的语法一致。

Filtering Values with exact values

对于精确词的匹配,includeexclude字段可以简单的对一个字符串数组进行复制,表示在索引中被查找的索引词。

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

Filtering Values with partitions

有时,在单个请求/响应对中要处理的唯一索引词太多,因此将分析分解为多个请求可能是最好的解决方案。这可以通过在查询时将字段值分组到多个分区中并在每个请求中只处理一个分区来实现。考虑这个请求,寻找最近没有登录任何访问的帐户:

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"
               }
            }
         }
      }
   }
}

This request is finding the last logged access date for a subset of customer accounts because we might want to expire some customer accounts who haven’t been seen for a long while. The num_partitions setting has requested that the unique account_ids are organized evenly into twenty partitions (0 to 19). and the partition setting in this request filters to only consider account_ids falling into partition 0. Subsequent requests should ask for partitions 1 then 2 etc to complete the expired-account analysis.

Note that the size setting for the number of results returned needs to be tuned with the num_partitions. For this particular account-expiration example the process for balancing values for size and num_partitions would be as follows:

Use the cardinality aggregation to estimate the total number of unique account_id values
Pick a value for num_partitions to break the number from 1) up into more manageable chunks
Pick a size value for the number of responses we want from each partition
Run a test request
If we have a circuit-breaker error we are trying to do too much in one request and must increase num_partitions. If the request was successful but the last account ID in the date-sorted test response was still an account we might want to expire then we may be missing accounts of interest and have set our numbers too low. We must either

increase the size parameter to return more results per partition (could be heavy on memory) or
increase the num_partitions to consider less accounts per request (could increase overall processing time as we need to make more requests)
Ultimately this is a balancing act between managing the Elasticsearch resources required to process a single request and the volume of requests that the client application must issue to complete a task.

Multi-field terms aggregation

terms aggregation不支持在同一个文档中采集多个索引词. 原因在于terms agg 并不支持采集索引词字符串本身的值, 而是利用global ordinals来生成一个字段中所有唯一值的列表,global ordinals可以带来重要的性能提升。但同时不能跨多字段聚合。

有下面2中方法在多个索引词上执行聚合:

Script

利用脚本在多个字段上重复进行索引词聚合,这会使global ordinals失效并且会比从一个字段上词啊挤索引词更慢。

copy_to field

如果你提前知道需要从哪些字段上采集索引词,就可以使用copy_to构造一个组合字段,可以在这个字段上执行聚合,并且可以利用global ordinals的优化效果。

未完待续。。。

猜你喜欢

转载自blog.csdn.net/weixin_43430036/article/details/83346836