45 · percentiles 与 cardinality 精度(近似聚合的真相)
阶段:第五阶段 / 进阶(第 20 篇指标聚合的深入)
ES:percentiles/percentile_ranks/cardinality| PostgreSQL:percentile_cont/COUNT(DISTINCT)
1. 概念:ES 的分位数和去重计数是“近似”的
为了在海量数据、多分片下还能快,ES 用了近似算法:
| 聚合 | 算法 | 特点 |
|---|---|---|
cardinality(去重计数) | HyperLogLog++ | 内存固定,有误差(可控) |
percentiles(分位数) | TDigest(或 HDR) | 极值附近更准,中间近似 |
它们不是精确值。数据量小时几乎无差,量大时要理解误差来源,别当精确数用于对账。
2. PostgreSQL 对照
-- 精确分位数(PG 精确,ES 近似)SELECTpercentile_cont(0.95)WITHINGROUP(ORDERBYlatency)FROMreq;-- 精确去重(PG 精确,ES cardinality 近似)SELECTCOUNT(DISTINCTuser_id)FROMreq;PG 是精确计算;ES 用近似换性能。要精确去重且量不大,可用
terms桶数或 composite。
3. ES DSL
3.1 percentiles(分位数)
GET req_idx/_search { "size": 0, "aggs": { "latency_pct": { "percentiles": { "field": "latency_ms", "percents": [50, 90, 95, 99] } } } }3.2 percentile_ranks(反查:某值排在第几百分位)
"aggs": { "rank": { "percentile_ranks": { "field": "latency_ms", "values": [200, 500] } } }3.3 cardinality(近似去重)+ 精度阈值
"aggs": { "uv": { "cardinality": { "field": "user_id", "precision_threshold": 3000 } } }precision_threshold:低于此基数时几乎精确,越大越准但越占内存(上限 40000)。
4. Spring Boot 实现
@ComponentpublicclassDoc45Percentiles{@AutowiredprivateElasticsearchClientelasticsearchClient;/** P50/P90/P95/P99 延迟 */publicMap<String,Double>latencyPercentiles(StringindexName)throwsIOException{SearchResponse<Void>resp=elasticsearchClient.search(s->s.index(indexName).size(0).aggregations("pct",a->a.percentiles(p->p.field("latency_ms").percents(50.0,90.0,95.0,99.0))),Void.class);// percentiles 结果是 key(分位)->value(值) 的 mapreturnresp.aggregations().get("pct").tdigestPercentiles().values().keyed();}/** 近似 UV,指定精度阈值 */publiclongapproxUv(StringindexName)throwsIOException{SearchResponse<Void>resp=elasticsearchClient.search(s->s.index(indexName).size(0).aggregations("uv",a->a.cardinality(c->c.field("user_id").precisionThreshold(3000))),Void.class);returnresp.aggregations().get("uv").cardinality().value();}}percentiles 默认 TDigest,读取用
.tdigestPercentiles();用 HDR 时读.hdrPercentiles()。keyed()返回Map<String,Double>(key 是 “95.0” 这样的字符串)。
5. 坑与最佳实践
- 近似 ≠ 精确:
cardinality/percentiles别用于财务对账等要求精确的场景。 cardinality调precision_threshold:按可接受误差和内存权衡,别无脑拉满。- percentiles 极值更准:P99/P1 比 P50 更可靠,这是 TDigest 的特性。
- 高延迟要 P99 而非 avg:平均值会被掩盖,SLO 看高分位。
- 要精确去重:小基数用
terms桶计数;大基数只能接受近似或离线精确算。