cardinality聚合专为估算唯一值数量设计,terms聚合枚举所有值再计数会导致内存爆炸和超时;前者基于HyperLogLog近似计算,高效可控,后者是精确分组而非计数工具。

直接用 cardinality 聚合,别想用 terms + top_hits 或 collapse 去“查出再数”,那是错方向。
为什么不能用 terms 聚合代替 cardinality
有人看到 terms 能列出所有去重值,就顺手加个 size: 10000 然后 count($response['aggregations']['my_terms']['buckets']) 来算总数——这在数据量稍大时立刻崩:
-
terms是精确枚举,内存和响应时间随唯一值数量线性增长;10 万个唯一值就可能 OOM 或超时 -
size默认只返回前 10 个桶,设成 10000 也仅是上限,ES 不保证能全取回(尤其分布式分片场景) - 它本质是“分组+取样”,不是为计数设计的;
cardinality才是专为“不重复数量估算”而生的概率算法
Laravel 中调用 ES cardinality 聚合的实操写法
用官方 PHP 客户端(elasticsearch/elasticsearch)发请求,核心就是构造一个带 cardinality 的聚合体。不需要额外封装,直接写数组结构即可:
$params = [
'index' => 'logs-index',
'body' => [
'size' => 0,
'aggs' => [
'unique_user_count' => [
'cardinality' => ['field' => 'user_id.keyword']
]
]
]
];
$response = $client->search($params);
$count = $response['aggregations']['unique_user_count']['value'] ?? 0;
-
size: 0必须加,避免混入 hits 干扰解析 -
user_id.keyword要带.keyword后缀,text 字段默认被分词,无法用于精确去重 - 如果字段是数字类型(如
user_id是 long),直接用user_id即可,不用加.keyword - 误差可控:
cardinality默认精度约 0.012%,加'precision_threshold' => 10000可进一步压低误差(但内存略增)
precision_threshold 参数怎么设才合理
这个参数不是越大越好,也不是随便填。它控制 HyperLogLog 内部寄存器数量,直接影响精度与内存消耗:
- 默认值是 3000,适用于大多数中小规模场景(百万级唯一值以内)
- 若预估唯一值在 10 万以内,设
10000就够用;超过 100 万,可提到40000,但再往上收益急剧下降 - 设成
100000会让单个聚合内存占用翻倍,而精度提升不到 0.1%,纯属浪费 - 线上建议先用默认值跑一周,监控
_cat/indices和聚合响应耗时,再决定是否调整
和 Laravel Redis HyperLogLog 方案怎么选
两者不是替代关系,而是分工明确:
- ES
cardinality:适合“已入库、需离线或准实时分析”的场景,比如每天凌晨跑一次昨日 UV;查询走 ES 聚合,不依赖外部服务 - Redis HyperLogLog(
PFADD/PFCOUNT):适合“写多读少、强实时、高并发”的在线统计,比如页面实时 UV 面板;但数据不在 ES 里,无法和其他字段做关联分析 - 千万别在 ES 查询里嵌套调用 Redis —— 网络开销、事务边界、错误传播都会让逻辑失控
- 真正需要两者联动时(如 ES 查出 top 用户,再用 Redis 查他们各自的行为基数),应拆成两个独立请求,在应用层组合
实际项目里,cardinality 的 key 名容易写错成 user_id 而非 user_id.keyword,一查永远是 0;这个点必须手敲确认,IDE 不会帮你校验字段后缀。


















