聚合可以做什么?

  1. count
  2. avg
  3. filter and count
  4. 每月新增
  5. top
  6. 是否存在不正常或不符合规则的数据

关键概念

Buckets

  • group by
  • 将数据按某种标准划分成不同集合
  • 桶嵌套: Cincinnati would be placed inside the Ohio state bucket, and the entire Ohio bucket would be placed inside the USA country bucket.

Metrics

  • count、avg、top
  • 统计桶中文档的指标

Because buckets can be nested, we can derive a much more complex aggregation:

  1. Partition documents by country (bucket).
  2. Then partition each country bucket by gender (bucket).
  3. Then partition each gender bucket by age ranges (bucket).
  4. Finally, calculate the average salary for each age range (metric)

    This will give you the average salary per <country, gender, age> combination.

嵌套桶

GET /cars/transactions/_search
{
"size" : 0,
"aggs": {
"colors": {
"terms": {
"field": "color"
},
"aggs": {
"avg_price": { "avg": { "field": "price" }
},
"make" : {
"terms" : {
"field" : "make"
},
"aggs" : {
"min_price" : { "min": { "field": "price"} },
"max_price" : { "max": { "field": "price"} }
}
}
}
}
}
}

条形图

按范围统计

GET /cars/transactions/_search
{
"size" : 0,
"aggs":{
"price":{
"histogram":{
"field": "price",
"interval": 20000
},
"aggs":{
"revenue": {
"sum": {
"field" : "price"
}
}
}
}
}
}
GET /cars/transactions/_search
{
"size" : 0,
"aggs": {
"makes": {
"terms": {
"field": "make",
"size": 10
},
"aggs": {
"stats": {
"extended_stats": {
"field": "price"
}
}
}
}
}
}

上述代码会按受欢迎度返回制造商列表以及它们各自的统计信息。我们对其中的 stats.avg 、 stats.count 和 stats.std_deviation 信息特别感兴趣,并用 它们计算出标准差:

std_err = std_deviation / count

按时间统计

GET /cars/transactions/_search
{
"size" : 0,
"aggs": {
"sales": {
"date_histogram": {
"field": "sold",
"interval": "month",
"format": "yyyy-MM-dd"
}
}
}
}

返回空Buckets

GET /cars/transactions/_search
{
"size" : 0,
"aggs": {
"sales": {
"date_histogram": {
"field": "sold",
"interval": "month",
"format": "yyyy-MM-dd",
"min_doc_count" : 0,
"extended_bounds" : {
"min" : "2014-01-01",
"max" : "2014-12-31"
}
}
}
}
}

范围限定的聚合

GET /cars/transactions/_search
{
"query" : {
"match" : {
"make" : "ford"
}
},
"aggs" : {
"colors" : {
"terms" : {
"field" : "color"
}
}
}
}

全局桶

全局 桶包含 所有 的文档,它无视查询的范围。因为它还是一个桶,我们可以像平常一样将聚合嵌套在内:

GET /cars/transactions/_search
{
"size" : 0,
"query" : {
"match" : {
"make" : "ford"
}
},
"aggs" : {
"single_avg_price": {
"avg" : { "field" : "price" }
},
"all": {
"global" : {},
"aggs" : {
"avg_price": {
"avg" : { "field" : "price" }
} }
}
}
}

过滤和聚合

选择合适类型的过滤(如:搜索命中、聚合或两者兼有)通常和我们期望如何表现用户交互有关。选择合适的过滤器(或组合)取决于我们期望如何将结果呈现给用户。

  • 在 filter 过滤中的 non-scoring 查询,同时影响搜索结果和聚合结果。
  • filter 桶影响聚合。
  • post_filter 只影响搜索结果。
  1. 过滤
GET /cars/transactions/_search
{
"size" : 0,
"query" : {
"constant_score": {
"filter": {
"range": {
"price": {
"gte": 10000
}
}
}
}
},
"aggs" : {
"single_avg_price": {
"avg" : { "field" : "price" }
}
}
}
  1. 过滤桶
GET /cars/transactions/_search
{
"size" : 0,
"query":{
"match": {
"make": "ford"
}
},
"aggs":{
"recent_sales": {
"filter": {
"range": {
"sold": {
"from": "now-1M"
}
}
},
"aggs": {
"average_price":{
"avg": {
"field": "price"
}
}
}
}
}
}
  1. post_filter
GET /cars/transactions/_search
{
"size" : 0,
"query": {
"match": {
"make": "ford"
}
},
"post_filter": {
"term" : {
"color" : "green"
}
},
"aggs" : {
"all_colors": {
"terms" : { "field" : "color" }
}
}
}
  • 性能考虑(Performance consideration)

    当你需要对搜索结果和聚合结果做不同的过滤时,你才应该使用 post_filter , 有时用户会在普通搜索使用 post_filter 。

不要这么做! post_filter 的特性是在查询 之后 执行,任何过滤对性能带来的好处(比如缓存)都会完全失去。

在我们需要不同过滤时, post_filter 只与聚合一起使用。

多桶排序

内置排序

GET /cars/transactions/_search
{
"size" : 0,
"aggs" : {
"colors" : {
"terms" : {
"field" : "color",
"order": {
"_count" : "asc"
}
}
}
}
}

我们为聚合引入了一个 order 对象, 它允许我们可以根据以下几个值中的一个值进行排序:

_count

按文档数排序。对 terms 、 histogram 、 date_histogram 有效。

_term

按词项的字符串值的字母顺序排序。只在 terms 内使用。

_key

按每个桶的键值数值排序(理论上与 _term 类似)。 只在 histogram 和 date_histogram 内使用。

按度量排序

GET /cars/transactions/_search
{
"size" : 0,
"aggs" : {
"colors" : {
"terms" : {
"field" : "color",
"order": {
"avg_price" : "asc"
}
},
"aggs": {
"avg_price": {
"avg": {"field": "price"}
}
}
}
}
} GET /cars/transactions/_search
{
"size" : 0,
"aggs" : {
"colors" : {
"terms" : {
"field" : "color",
"order": {
"stats.variance" : "asc"
}
},
"aggs": {
"stats": {
"extended_stats": {"field": "price"}
}
}
}
}
}

基于“深度”度量排序

GET /cars/transactions/_search
{
"size" : 0,
"aggs" : {
"colors" : {
"histogram" : {
"field" : "price",
"interval": 20000,
"order": {
"red_green_cars>stats.variance" : "asc"
}
},
"aggs": {
"red_green_cars": {
"filter": { "terms": {"color": ["red", "green"]}},
"aggs": {
"stats": {"extended_stats": {"field" : "price"}}
}
}
}
}
}
}

创建一个汽车售价的直方图,但是按照红色和绿色(不包括蓝色)车各自的方差来排序

近似聚合

去重统计、百分位计算都是一个大概的统计,要求越精确,占用的资源越高。

去重统计

字段按哈希值存储

GET /cars/transactions/_search
{
"size" : 0,
"aggs" : {
"distinct_colors" : {
"cardinality" : {
"field" : "color",
"precision_threshold" : 100
}
}
}
}

precision_threshold 接受 0–40,000 之间的数字,更大的值还是会被当作 40,000 来处理。

示例会确保当字段唯一值在 100 以内时会得到非常准确的结果。尽管算法是无法保证这点的,但如果基数在阈值以下,几乎总是 100% 正确的。高于阈值的基数会开始节省内存而牺牲准确度,同时也会对度量结果带入误差。

对于指定的阈值,HLL 的数据结构会大概使用 precision_threshold * 8 字节的内存,所以就必须在牺牲内存和获得额外的准确度间做平衡。

在实际应用中, 100 的阈值可以在唯一值为百万的情况下仍然将误差维持 5% 以内。

速度优化

预先计算哈希值,只对内容很长或者基数很高的字段有用,计算这些字段的哈希值的消耗在查询时是无法忽略的。

PUT /cars/
{
"mappings": {
"transactions": {
"properties": {
"color": {
"type": "string",
"fields": {
"hash": {
"type": "murmur3"
}
}
}
}
}
}
} GET /cars/transactions/_search
{
"size" : 0,
"aggs" : {
"distinct_colors" : {
"cardinality" : {
"field" : "color.hash"
}
}
}
}

百分位计算

百分位数展现某以具体百分比下观察到的数值。

百分位数通常用来找出异常。在(统计学)的正态分布下,第 0.13 和 第 99.87 的百分位数代表与均值距离三倍标准差的值。任何处于三倍标准差之外的数据通常被认为是不寻常的,因为它与平均值相差太大。

GET /website/logs/_search
{
"size" : 0,
"aggs" : {
"load_times" : {
"percentiles" : {
"field" : "latency"
}
},
"avg_load_time" : {
"avg" : {
"field" : "latency"
}
}
}
} GET /website/logs/_search
{
"size" : 0,
"aggs" : {
"zones" : {
"terms" : {
"field" : "zone"
},
"aggs" : {
"load_times" : {
"percentiles" : {
"field" : "latency",
"percents" : [50, 95.0, 99.0]
}
},
"load_avg" : {
"avg" : {
"field" : "latency"
}
}
}
}
}
}

percentiles 度量告诉我们落在某个百分比以下的所有文档的最小值。例如,如果 50 百分位是 119ms,那么有 50% 的文档数值都不超过 119ms。 percentile_ranks 告诉我们某个具体值属于哪个百分位。119ms 的 percentile_ranks 是在 50 百分位。 这基本是个双向关系,例如:

50 百分位是 119ms。

119ms 百分位等级是 50 百分位。

GET /website/logs/_search
{
"size" : 0,
"aggs" : {
"zones" : {
"terms" : {
"field" : "zone"
},
"aggs" : {
"load_times" : {
"percentile_ranks" : {
"field" : "latency",
"values" : [210, 800]
}
}
}
}
}
}

算法的特性:

  • 百分位的准确度与百分位的 极端程度 相关,也就是说 1 或 99 的百分位要比 50 百分位要准确。这只是数据结构内部机制的一种特性,但这是一个好的特性,因为多数人只关心极端的百分位。
  • 对于数值集合较小的情况,百分位非常准确。如果数据集足够小,百分位可能 100% 精确。
  • 随着桶里数值的增长,算法会开始对百分位进行估算。它能有效在准确度和内存节省之间做出权衡。 不准确的程度比较难以总结,因为它依赖于 聚合时数据的分布以及数据量的大小。

与 cardinality 类似,我们可以通过修改参数 compression 来控制内存与准确度之间的比值。

TDigest 算法用节点近似计算百分比:节点越多,准确度越高(同时内存消耗也越大),这都与数据量成正比。 compression 参数限制节点的最大数目为 20 * compression 。

因此,通过增加压缩比值,可以以消耗更多内存为代价提高百分位数准确性。更大的压缩比值会使算法运行更慢,因为底层的树形数据结构的存储也会增长,也导致操作的代价更高。默认的压缩比值是 100 。

通过聚合发现异常指标

significant_terms ,我们想要显著的共性(注:uncommonly common)

而不想要最流行的

  • 基于统计的推荐(Recommending Based on Statistics)编辑

    统计异常就是与统计背景相比在前景特征组中过度展现的那些影片

Doc Values and Fielddata

倒排索引在搜索时非常快速,但是在按字段排序时不理想。需要转置倒排索引--即列存储。

Doc Values

列存储将所有单字段的值存储在单数据列中,对其进行操作是十分高效的,例如排序。

在 Elasticsearch 中,Doc Values 就是一种列式存储结构,默认情况下每个字段的 Doc Values 都是激活的,Doc Values 是在索引时创建的,当字段索引时,Elasticsearch 为了能够快速检索,会把字段的值加入倒排索引中,同时它也会存储该字段的 Doc Values

Elasticsearch 中的 Doc Values 常被应用到以下场景:

  • 对一个字段进行排序
  • 对一个字段进行聚合
  • 某些过滤,比如地理位置过滤
  • 某些与字段相关的脚本计算

    因为文档值被序列化到磁盘,我们可以依靠操作系统的帮助来快速访问。

Fielddata

doc values 不生成分析的字符串,然而,这些字段仍然可以使用聚合,那怎么可能呢?

答案是一种被称为 fielddata 的数据结构。与 doc values 不同,fielddata 构建和管理 100% 在内存中,常驻于 JVM 内存堆。这意味着它本质上是不可扩展的,有很多边缘情况下要提防。

在分析字符串上下文中 fielddata 的挑战?

高基数字段在加载到 fielddata 时会消耗大量内存。 分析的过程会经常(尽管不总是这样)生成大量的 token,这些 token 大多都是唯一的。 这会增加字段的整体基数并且带来更大的内存压力。

  • Fielddata 是 延迟 加载。

  • 选择堆大小(Choosing a Heap Size)

    • 不要超过可用 RAM 的 50%
    • 不要超过 32 GB

断路器

断路器通过内部检查(字段的类型、基数、大小等等)来估算一个查询需要的内存。它然后检查要求加载的 fielddata 是否会导致 fielddata 的总量超过堆的配置比例。

如果估算查询的大小超出限制,就会 触发 断路器,查询会被中止并返回异常。这都发生在数据加载 之前 ,也就意味着不会引起 OutOfMemoryException 。

在 Fielddata的大小 中,我们提过关于给 fielddata 的大小加一个限制,从而确保旧的无用 fielddata 被回收的方法。 indices.fielddata.cache.size 和 indices.breaker.fielddata.limit 之间的关系非常重要。 如果断路器的限制低于缓存大小,没有数据会被回收。为了能正常工作,断路器的限制 必须 要比缓存大小要高。

Fielddata 的过滤

PUT /music/_mapping/song
{
"properties": {
"tag": {
"type": "string",
"fielddata": {
"filter": {
"frequency": {
"min": 0.01,
"min_segment_size": 500
}
}
}
}
}
}

Fielddata 过滤对内存使用有 巨大的 影响,权衡也是显而易见的:我们实际上是在忽略数据。但对于很多应用,这种权衡是合理的,因为这些数据根本就没有被使用到。内存的节省通常要比包括一个大量而无用的长尾项更为重要

预加载fileddata

有三种方式可以解决这个延时高峰:

  • 预加载 fielddata
  • 预加载全局序号
  • 缓存预热

    所有的变化都基于同一概念:预加载 fielddata,这样在用户进行搜索时就不会碰到延迟高峰。

优化聚合查询

深度优先与广度优先(Depth-First Versus Breadth-First)

如果我们想要查询出演影片最多的十个演员以及与他们合作最多的演员。

  • 默认深度优先
  • 特殊场景采用广度优先:
    1. 广度优先仅仅适用于每个组的聚合数量远远小于当前总文档数的情况下,因为广度优先会在内存中缓存裁剪后的仅仅需要缓存的每个组的所有数据,以便于它的子聚合分组查询可以复用上级聚合的数据。
{
"aggs" : {
"actors" : {
"terms" : {
"field" : "actors",
"size" : 10,
"collect_mode" : "breadth_first"
},
"aggs" : {
"costars" : {
"terms" : {
"field" : "actors",
"size" : 5
}
}
}
}
}
}

总结

聚合给 Elasticsearch带来了难以言喻的强大能力和灵活性。桶与度量的嵌套能力,基数与百分位数的快速估算能力,定位信息中统计异常的能力,

Elasticsearch 默认给 大多数 字段启用 doc values,所以在一些搜索场景大大的节省了内存使用量,但是需要注意的是只有不分词的 string 类型的字段才能使用这种特性。

内存的管理形式可以有多种形式,这取决于我们特定的应用场景:

  1. 在规划时,组织好数据,使聚合运行在 not_analyzed 字符串而不是 analyzed 字符串,这样可以有效的利用 doc values 。
  2. 在测试时,验证分析链不会在之后的聚合计算中创建高基数字段。(见聚合与分析中 高基数内存的影响(High-Cardinality Memory Implications) )
  3. 在搜索时,合理利用近似聚合和数据过滤。
  4. 在节点层,设置硬内存大小以及动态的断熔限制。
  5. 在应用层,通过监控集群内存的使用情况和 Full GC 的发生频率,来调整是否需要给集群资源添加更多的机器节点

参考文献

es聚合学习笔记的更多相关文章

  1. OpenGL ES学习笔记(三)——纹理

    首先申明下,本文为笔者学习<OpenGL ES应用开发实践指南(Android卷)>的笔记,涉及的代码均出自原书,如有需要,请到原书指定源码地址下载. <OpenGL ES学习笔记( ...

  2. mongoDB 学习笔记纯干货(mongoose、增删改查、聚合、索引、连接、备份与恢复、监控等等)

    最后更新时间:2017-07-13 11:10:49 原始文章链接:http://www.lovebxm.com/2017/07/13/mongodb_primer/ MongoDB - 简介 官网: ...

  3. Dubbo -- 系统学习 笔记 -- 示例 -- 分组聚合

    Dubbo -- 系统学习 笔记 -- 目录 示例 想完整的运行起来,请参见:快速启动,这里只列出各种场景的配置方式 分组聚合 按组合并返回结果,比如菜单服务,接口一样,但有多种实现,用group区分 ...

  4. OpenGL ES学习笔记(二)——平滑着色、自适应宽高及三维图像生成

    首先申明下,本文为笔者学习<OpenGL ES应用开发实践指南(Android卷)>的笔记,涉及的代码均出自原书,如有需要,请到原书指定源码地址下载. <Android学习笔记--O ...

  5. 【转】mongoDB 学习笔记纯干货(mongoose、增删改查、聚合、索引、连接、备份与恢复、监控等等)

    mongoDB 学习笔记纯干货(mongoose.增删改查.聚合.索引.连接.备份与恢复.监控等等) http://www.cnblogs.com/bxm0927/p/7159556.html

  6. OpenGL ES 学习笔记 - Overview - 小旋的博客

    移动端图形标准中,目前 OpenGL ES 仍然是比较通用的标准(Vulkan 则是新一代),这里新开一个系列用于记录学习 OpenGL ES 的历程,以便查阅理解. OverView OpenGL ...

  7. hive学习笔记之十:用户自定义聚合函数(UDAF)

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本文是<hive学习笔记>的第十 ...

  8. Caliburn.Micro学习笔记(三)----事件聚合IEventAggregator和 Ihandle<T>

    Caliburn.Micro学习笔记目录 今天 说一下Caliburn.Micro的IEventAggregator和IHandle<T>分成两篇去讲这一篇写一个简单的例子 看一它的的实现 ...

  9. SqlServer学习笔记【暂】

    Sql学习笔记,暂时先保存在着,等不忙了再整理成章节,如果其中有问题的,还请各位大神不吝赐教! --------------------------------------所有的数据基于Northwi ...

随机推荐

  1. MySQL中的聚集索引和辅助索引

    MySQL中的聚集索引和辅助索引 当你定义一个主键时,innodb存储引擎就把他当做聚集索引 如果你没有定义一个主键,则innodb定位到第一个唯一索引,且改索引的所有列值均为非空,就将其当做聚集索引 ...

  2. 批量导出docker images 的一个简单方法

    docker images |cut -c - |xargs docker save -o iamges.tar 主要 最大的长度不能超过 18 超过了就得改一下 -c 后面的数据长度 最终效果为: ...

  3. Spring学习笔记(一)

    Spring学习笔记(一) 这是一个沉淀的过程,大概第一次接触Spring是在去年的这个时候,当初在实训,初次接触Java web,直接学习SSM框架(当是Servlet都没有学),于是,养成了一个很 ...

  4. Linux系列(2):入门之线上求助

    前言:Linux命令那么多,你是否为记不住Linux的命令而烦恼呢? 这一章节就是来解决这个问题的. 1.Linux系统的线上求助 1.指令补全 在上一章节提到过使用[Tab]快捷键可以根据用户输入的 ...

  5. $listeners 在vue中的使用 --初学

    事件回传之 $listeners 组件由下向上回传事件 <!doctype html><html lang="en"> <head> <m ...

  6. Spring4学习回顾之路01—HelloWorld

    以前公司一直使用的是spring3.0,最近一段时间开始用了4.0,官网上都已经有了5.0,但是很多知识点已经忘了差不多了,趁现在项目不忙写写随笔,一来回顾自己的知识点,二来如果能帮助比我还小白的小白 ...

  7. PAT A1012 Best Rank(25)

    题目描述 To evaluate the performance of our first year CS majored students, we consider their grades of ...

  8. POJ - 3249 Test for Job (在DAG图利用拓扑排序中求最长路)

    (点击此处查看原题) 题意 给出一个有n个结点,m条边的DAG图,每个点都有权值,每条路径(注意不是边)的权值为其经过的结点的权值之和,每条路径总是从入度为0的点开始,直至出度为0的点,问所有路径中权 ...

  9. Laravel-admin 消息提醒、播放音频、点击跳转

    jquery-toastr 消息提醒.播放音频.点击跳转 应用情景,有新的订单生成,后台进行消息提醒并播放音频(这里用到轮询简单实现):下面附代码 1.找到laravel-admin 中的 index ...

  10. jquery的scrollTop方法

    scrollTop方法设置或返回备选元素的垂直滚动条位置. 提示:当滚动条位于最顶部时,位置是0 当用于返回位置时: 该方法返回第一个匹配元素的滚动条的垂直位置 当用于设置位置时: 该方法设置所有匹配 ...