基数聚合编辑

一种 单值 指标聚合,用于计算不同值的近似计数。

假设您正在索引商店销售额,并且想要计算与查询匹配的已售出产品的唯一数量

response = client.search(
  index: 'sales',
  size: 0,
  body: {
    aggregations: {
      type_count: {
        cardinality: {
          field: 'type'
        }
      }
    }
  }
)
puts response
POST /sales/_search?size=0
{
  "aggs": {
    "type_count": {
      "cardinality": {
        "field": "type"
      }
    }
  }
}

响应

{
  ...
  "aggregations": {
    "type_count": {
      "value": 3
    }
  }
}

精度控制编辑

此聚合还支持 precision_threshold 选项

response = client.search(
  index: 'sales',
  size: 0,
  body: {
    aggregations: {
      type_count: {
        cardinality: {
          field: 'type',
          precision_threshold: 100
        }
      }
    }
  }
)
puts response
POST /sales/_search?size=0
{
  "aggs": {
    "type_count": {
      "cardinality": {
        "field": "type",
        "precision_threshold": 100 
      }
    }
  }
}

precision_threshold 选项允许您在内存和准确性之间进行权衡,并定义一个唯一计数,低于该计数的计数预计接近准确。高于此值,计数可能会变得更加模糊。最大支持值为 40000,高于此数字的阈值将与 40000 的阈值具有相同的效果。默认值为 3000

计数是近似的编辑

计算精确计数需要将值加载到哈希集中并返回其大小。当处理高基数集和/或大值时,这无法扩展,因为所需的内存使用量以及在节点之间通信这些每个分片集的需求将占用集群过多的资源。

cardinality 聚合基于 HyperLogLog++ 算法,该算法根据值的哈希值进行计数,具有一些有趣的特性

  • 可配置的精度,它决定了如何在内存和准确性之间进行权衡,
  • 对低基数集具有出色的准确性,
  • 固定的内存使用量:无论是有数十个还是数十亿个唯一值,内存使用量仅取决于配置的精度。

对于精度阈值为 c,我们正在使用的实现需要大约 c * 8 字节。

下图显示了阈值前后误差的变化情况

cardinality error

对于所有 3 个阈值,计数都已准确到配置的阈值。虽然不能保证,但这很可能是这种情况。实际的准确性取决于相关的数据集。一般来说,大多数数据集都显示出一致的良好准确性。另请注意,即使阈值低至 100,即使在计算数百万个项目时,误差仍然非常低(如上图所示,为 1-6%)。

HyperLogLog++ 算法依赖于哈希值的前导零,数据集中哈希值的精确分布会影响基数的准确性。

预先计算的哈希值编辑

在具有高基数的字符串字段上,将字段值的哈希值存储在索引中,然后在此字段上运行基数聚合可能会更快。这可以通过从客户端提供哈希值来完成,也可以通过使用 mapper-murmur3 插件让 Elasticsearch 为您计算哈希值来完成。

预先计算哈希值通常仅在非常大和/或高基数的字段上有用,因为它可以节省 CPU 和内存。但是,在数字字段上,哈希运算非常快,并且存储原始值所需的内存与存储哈希值所需的内存一样多或更少。在低基数字符串字段上也是如此,特别是考虑到这些字段经过优化以确保每个段中每个唯一值最多计算一次哈希值。

脚本编辑

如果需要两个字段组合的基数,请创建一个 运行时字段 来组合它们并对其进行聚合。

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

response = client.search(
  index: 'sales',
  size: 0,
  body: {
    aggregations: {
      tag_cardinality: {
        cardinality: {
          field: 'tag',
          missing: 'N/A'
        }
      }
    }
  }
)
puts response
POST /sales/_search?size=0
{
  "aggs": {
    "tag_cardinality": {
      "cardinality": {
        "field": "tag",
        "missing": "N/A" 
      }
    }
  }
}

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

执行提示编辑

您可以使用不同的机制运行基数聚合

  • 直接使用字段值 (direct)
  • 使用字段的全局序号并在完成分片后解析这些值 (global_ordinals)
  • 使用段序号值并在每个段后解析这些值 (segment_ordinals)

此外,还有两种“基于启发式”的模式。这些模式将导致 Elasticsearch 使用有关索引状态的一些数据来选择适当的执行方法。这两种启发式方法是

  • save_time_heuristic - 这是 Elasticsearch 8.4 及更高版本中的默认值。
  • save_memory_heuristic - 这是 Elasticsearch 8.3 及更早版本中的默认值

如果未指定,Elasticsearch 将应用启发式方法来选择适当的模式。另请注意,对于某些数据(非序号字段),direct 是唯一选项,在这些情况下,提示将被忽略。一般来说,不需要设置此值。