罕见词项聚合编辑

一种基于多桶值源的聚合,用于查找“罕见”词项——分布尾部的、不常见的词项。从概念上讲,这就像一个 terms 聚合,按 _count 升序排序。如 terms 聚合文档 中所述,实际上按计数升序对 terms 聚合进行排序会导致无限错误。相反,您应该使用 rare_terms 聚合

语法编辑

一个 rare_terms 聚合在孤立情况下看起来像这样

{
  "rare_terms": {
    "field": "the_field",
    "max_doc_count": 1
  }
}

表 50. rare_terms 参数

参数名称

描述

必需

默认值

field

我们希望在其中查找罕见词项的字段

必需

max_doc_count

词项在其中出现的最大文档数量。

可选

1

precision

内部 CuckooFilters 的精度。较小的精度会导致更好的近似值,但内存使用量更高。不能小于 0.00001

可选

0.001

include

应包含在聚合中的词项

可选

exclude

应从聚合中排除的词项

可选

missing

如果文档没有被聚合的字段,则应使用的值

可选

示例

response = client.search(
  body: {
    aggregations: {
      genres: {
        rare_terms: {
          field: 'genre'
        }
      }
    }
  }
)
puts response
GET /_search
{
  "aggs": {
    "genres": {
      "rare_terms": {
        "field": "genre"
      }
    }
  }
}

响应

{
  ...
  "aggregations": {
    "genres": {
      "buckets": [
        {
          "key": "swing",
          "doc_count": 1
        }
      ]
    }
  }
}

在此示例中,我们看到的唯一桶是“swing”桶,因为它是在一个文档中出现的唯一词项。如果我们将 max_doc_count 增加到 2,我们将看到更多桶

response = client.search(
  body: {
    aggregations: {
      genres: {
        rare_terms: {
          field: 'genre',
          max_doc_count: 2
        }
      }
    }
  }
)
puts response
GET /_search
{
  "aggs": {
    "genres": {
      "rare_terms": {
        "field": "genre",
        "max_doc_count": 2
      }
    }
  }
}

现在显示了 doc_count 为 2 的“jazz”词项

{
  ...
  "aggregations": {
    "genres": {
      "buckets": [
        {
          "key": "swing",
          "doc_count": 1
        },
        {
          "key": "jazz",
          "doc_count": 2
        }
      ]
    }
  }
}

最大文档计数编辑

max_doc_count 参数用于控制词项可以具有的文档计数上限。与 terms 聚合不同,rare_terms 聚合没有大小限制。这意味着满足 max_doc_count 标准的词项将被返回。聚合以这种方式工作,以避免影响 terms 聚合的按升序排序问题。

但是,这意味着如果选择不当,可能会返回大量结果。为了限制此设置的危险性,最大 max_doc_count 为 100。

最大桶限制编辑

由于罕见词项聚合的工作方式,它比其他聚合更容易触发 search.max_buckets 软限制。在聚合收集结果时,max_bucket 软限制在每个分片的基础上进行评估。一个词项可能在一个分片上是“罕见的”,但在所有分片结果合并在一起后就变得“不罕见”。这意味着单个分片往往会收集比真正罕见的桶更多的桶,因为它们只有自己的本地视图。此列表最终会在协调节点上被修剪为正确、更小的罕见词项列表……但分片可能已经触发了 max_buckets 软限制并中止了请求。

在对可能包含许多“罕见”词项的字段进行聚合时,您可能需要增加 max_buckets 软限制。或者,您可能需要找到一种方法来过滤结果以返回更少的罕见值(更短的时间跨度、按类别过滤等),或者重新评估您对“罕见”的定义(例如,如果某事物出现了 100,000 次,它真的是“罕见的”吗?)

文档计数是近似的编辑

确定数据集中“罕见”词项的简单方法是将所有值放入一个映射中,在访问每个文档时递增计数,然后返回最下面的 n 行。这甚至无法扩展到中等规模的数据集。一种分片方法,其中只保留每个分片的“前 n”个值(类似于 terms 聚合)会失败,因为问题的长尾性质意味着在不简单地收集所有分片的所有值的情况下,不可能找到“前 n”个最下面的值。

相反,罕见词项聚合使用不同的近似算法

  1. 第一次看到值时,它们会被放入一个映射中。
  2. 词项的每次额外出现都会递增映射中的计数器
  3. 如果计数器 > max_doc_count 阈值,则该词项将从映射中删除并放入 CuckooFilter
  4. 在每个词项上都会查询 CuckooFilter。如果该值在过滤器中,则已知它已超过阈值并被跳过。

执行后,值的映射是 max_doc_count 阈值下“罕见”词项的映射。然后,此映射和 CuckooFilter 与所有其他分片合并。如果有超过阈值的词项(或出现在不同分片的 CuckooFilter 中),则该词项将从合并列表中删除。最终值的映射将作为“罕见”词项返回给用户。

CuckooFilters 有可能返回假阳性(它们可能会说一个值存在于它们的集合中,而实际上它不存在)。由于 CuckooFilter 用于查看词项是否超过阈值,这意味着 CuckooFilter 的假阳性会错误地认为一个值是常见的,而实际上它不是(因此将其从最终的桶列表中排除)。实际上,这意味着聚合表现出假阴性行为,因为过滤器正在被“反向”使用,与人们通常认为的近似集合成员草图不同。

CuckooFilters 在论文中进行了更详细的描述

Fan, Bin, et al. "Cuckoo filter: Practically better than bloom." Proceedings of the 10th ACM International on Conference on emerging Networking Experiments and Technologies. ACM, 2014.

精度编辑

虽然内部 CuckooFilter 本质上是近似的,但可以使用 precision 参数控制假阴性率。这允许用户用更多的运行时内存换取更准确的结果。

默认精度为 0.001,最小精度(例如,最准确且内存开销最大)为 0.00001。以下是一些图表,展示了聚合的准确性如何受精度和不同词项数量的影响。

X 轴显示聚合已看到的不同值的数量,Y 轴显示百分比误差。每个线系列代表一个“稀有度”条件(从一个稀有项目到 100,000 个稀有项目)。例如,橙色的“10”线意味着十个值是“稀有的”(doc_count == 1),在 1-2000 万个不同值中(其中其余值具有 doc_count > 1

第一个图表显示精度 0.01

accuracy 01

以及精度 0.001(默认值)

accuracy 001

最后是 precision 0.0001

accuracy 0001

默认精度 0.001 为测试条件保持 < 2.5% 的准确性,并且随着不同值数量的增加,准确性以受控的线性方式缓慢下降。

默认精度 0.001 的内存配置文件为 1.748⁻⁶ * n 字节,其中 n 是聚合已看到的不同值的数量(也可以粗略地估计,例如,2000 万个唯一值约为 30 MB 内存)。无论选择哪种精度,内存使用量都与不同值的数量成线性关系,精度只影响内存配置文件的斜率,如该图表所示

memory

为了比较,2000 万个桶的等效 terms 聚合将大约为 20m * 69b == ~1.38gb(69 字节是对空桶成本的非常乐观的估计,远低于断路器所考虑的成本)。因此,虽然 rare_terms 聚合相对较重,但它仍然比等效的 terms 聚合小几个数量级

过滤值编辑

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

使用正则表达式过滤值编辑

response = client.search(
  body: {
    aggregations: {
      genres: {
        rare_terms: {
          field: 'genre',
          include: 'swi*',
          exclude: 'electro*'
        }
      }
    }
  }
)
puts response
GET /_search
{
  "aggs": {
    "genres": {
      "rare_terms": {
        "field": "genre",
        "include": "swi*",
        "exclude": "electro*"
      }
    }
  }
}

在上面的示例中,将为所有以 swi 开头的标签创建桶,但以 electro 开头的标签除外(因此标签 swing 将被聚合,但 electro_swing 不会)。include 正则表达式将确定哪些值“允许”被聚合,而 exclude 确定哪些值不应该被聚合。当两者都被定义时,exclude 优先,这意味着,include 首先被评估,然后才是 exclude

语法与 正则表达式查询 相同。

使用精确值过滤值编辑

对于基于精确值的匹配,includeexclude 参数可以简单地接受一个字符串数组,这些字符串代表索引中找到的词项

response = client.search(
  body: {
    aggregations: {
      genres: {
        rare_terms: {
          field: 'genre',
          include: [
            'swing',
            'rock'
          ],
          exclude: [
            'jazz'
          ]
        }
      }
    }
  }
)
puts response
GET /_search
{
  "aggs": {
    "genres": {
      "rare_terms": {
        "field": "genre",
        "include": [ "swing", "rock" ],
        "exclude": [ "jazz" ]
      }
    }
  }
}

缺失值编辑

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

response = client.search(
  body: {
    aggregations: {
      genres: {
        rare_terms: {
          field: 'genre',
          missing: 'N/A'
        }
      }
    }
  }
)
puts response
GET /_search
{
  "aggs": {
    "genres": {
      "rare_terms": {
        "field": "genre",
        "missing": "N/A" 
      }
    }
  }
}

没有 tags 字段值的文档将与具有值 N/A 的文档落入同一个桶中。

嵌套、罕见词项和评分子聚合编辑

RareTerms 聚合必须在 breadth_first 模式下运行,因为它需要在文档计数阈值被突破时修剪术语。此要求意味着 RareTerms 聚合与某些需要 depth_first 模式的聚合组合不兼容。特别是,位于 nested 内部的评分子聚合会强制整个聚合树以 depth_first 模式运行。由于 RareTerms 无法处理 depth_first,这将抛出异常。

举个具体的例子,如果 rare_terms 聚合是 nested 聚合的子聚合,并且 rare_terms 的某个子聚合需要文档评分(比如 top_hits 聚合),这将抛出异常。