基数聚合
编辑基数聚合
编辑一个 单值
指标聚合,用于计算不同值的近似计数。
假设您正在索引商店销售数据,并且想要计算与查询匹配的已售出产品的唯一数量
resp = client.search( index="sales", size="0", aggs={ "type_count": { "cardinality": { "field": "type" } } }, ) print(resp)
response = client.search( index: 'sales', size: 0, body: { aggregations: { type_count: { cardinality: { field: 'type' } } } } ) puts response
const response = await client.search({ index: "sales", size: 0, aggs: { type_count: { cardinality: { field: "type", }, }, }, }); console.log(response);
POST /sales/_search?size=0 { "aggs": { "type_count": { "cardinality": { "field": "type" } } } }
响应
{ ... "aggregations": { "type_count": { "value": 3 } } }
精度控制
编辑此聚合还支持 precision_threshold
选项
resp = client.search( index="sales", size="0", aggs={ "type_count": { "cardinality": { "field": "type", "precision_threshold": 100 } } }, ) print(resp)
response = client.search( index: 'sales', size: 0, body: { aggregations: { type_count: { cardinality: { field: 'type', precision_threshold: 100 } } } } ) puts response
const response = await client.search({ index: "sales", size: 0, aggs: { type_count: { cardinality: { field: "type", precision_threshold: 100, }, }, }, }); console.log(response);
计数是近似的
编辑计算精确计数需要将值加载到哈希集中并返回其大小。当处理高基数集和/或大值时,这无法扩展,因为所需的内存使用量以及在节点之间传递每个分片集的需要会利用集群的太多资源。
此 cardinality
聚合基于 HyperLogLog++ 算法,该算法基于值的哈希进行计数,并具有一些有趣的特性
- 可配置的精度,决定如何在内存和准确性之间进行权衡,
- 在低基数集上具有出色的准确性,
- 固定的内存使用量:无论是否存在数十个还是数十亿个唯一值,内存使用量仅取决于配置的精度。
对于 c
的精度阈值,我们正在使用的实现需要大约 c * 8
个字节。
下图显示了误差在阈值之前和之后如何变化
对于所有 3 个阈值,计数在配置的阈值之前都是准确的。虽然不能保证,但这种情况很可能发生。实际的准确性取决于所讨论的数据集。一般来说,大多数数据集都显示出始终如一的良好准确性。另请注意,即使阈值低至 100,即使在计数数百万个项目时,误差仍然非常低(如上图所示的 1-6%)。
HyperLogLog++ 算法取决于哈希值的前导零,数据集中哈希的精确分布会影响基数的准确性。
预先计算的哈希
编辑对于具有高基数的字符串字段,将字段值的哈希存储在索引中,然后在此字段上运行基数聚合可能会更快。这可以通过从客户端提供哈希值来完成,也可以通过使用 mapper-murmur3
插件让 Elasticsearch 为您计算哈希值来完成。
预先计算哈希通常仅在非常大和/或高基数字段上才有用,因为它节省了 CPU 和内存。但是,在数字字段上,哈希速度非常快,并且存储原始值所需的内存与存储哈希一样多或更少。对于低基数字符串字段也是如此,尤其是考虑到这些字段具有优化,以确保每个段的每个唯一值最多计算一次哈希。
脚本
编辑如果您需要两个字段组合的基数,请创建一个组合它们的运行时字段并对其进行聚合。
resp = client.search( index="sales", size="0", runtime_mappings={ "type_and_promoted": { "type": "keyword", "script": "emit(doc['type'].value + ' ' + doc['promoted'].value)" } }, aggs={ "type_promoted_count": { "cardinality": { "field": "type_and_promoted" } } }, ) print(resp)
response = client.search( index: 'sales', size: 0, body: { runtime_mappings: { type_and_promoted: { type: 'keyword', script: "emit(doc['type'].value + ' ' + doc['promoted'].value)" } }, aggregations: { type_promoted_count: { cardinality: { field: 'type_and_promoted' } } } } ) puts response
const response = await client.search({ index: "sales", size: 0, runtime_mappings: { type_and_promoted: { type: "keyword", script: "emit(doc['type'].value + ' ' + doc['promoted'].value)", }, }, aggs: { type_promoted_count: { cardinality: { field: "type_and_promoted", }, }, }, }); console.log(response);
POST /sales/_search?size=0 { "runtime_mappings": { "type_and_promoted": { "type": "keyword", "script": "emit(doc['type'].value + ' ' + doc['promoted'].value)" } }, "aggs": { "type_promoted_count": { "cardinality": { "field": "type_and_promoted" } } } }
缺失值
编辑missing
参数定义了应如何处理缺少值的文档。默认情况下,它们将被忽略,但也可以将它们视为具有值。
resp = client.search( index="sales", size="0", aggs={ "tag_cardinality": { "cardinality": { "field": "tag", "missing": "N/A" } } }, ) print(resp)
response = client.search( index: 'sales', size: 0, body: { aggregations: { tag_cardinality: { cardinality: { field: 'tag', missing: 'N/A' } } } } ) puts response
const response = await client.search({ index: "sales", size: 0, aggs: { tag_cardinality: { cardinality: { field: "tag", missing: "N/A", }, }, }, }); console.log(response);
执行提示
编辑您可以使用不同的机制运行基数聚合
- 直接使用字段值 (
direct
) - 通过使用字段的全局序号并在完成分片后解析这些值 (
global_ordinals
) - 通过使用段序号值并在每个段之后解析这些值 (
segment_ordinals
)
此外,还有两种“基于启发式”的模式。这些模式将导致 Elasticsearch 使用一些关于索引状态的数据来选择适当的执行方法。这两个启发式方法是
-
save_time_heuristic
- 这是 Elasticsearch 8.4 及更高版本中的默认值。 -
save_memory_heuristic
- 这是 Elasticsearch 8.3 及更早版本中的默认值
如果未指定,Elasticsearch 将应用启发式方法来选择适当的模式。另请注意,对于某些数据(非序号字段),direct
是唯一选项,在这种情况下,提示将被忽略。一般来说,没有必要设置此值。