cardinality度量是一个近似算法。它是基于HyperLogLog++(HLL)算法的。HLL 会先对我们的输入作哈希运算,然后根据哈希运算的结果中的 bits 做概率估算从而得到基数。
我们不需要理解技术细节(如果确实感兴趣,可以阅读这篇论文),但我们最好应该关注一下这个算法的特性:
可配置的精度,用来控制内存的使用(更精确 = 更多内存)。
小的数据集精度是非常高的。
我们可以通过配置参数,来设置去重需要的固定内存使用量。无论数千还是数十亿的唯一值,内存使用量只与你配置的精确度相关。
要配置精度,我们必须指定precision_threshold参数的值。这个阈值定义了在何种基数水平下我们希望得到一个近乎精确的结果。参考以下示例:
precision_threshold接受 0–40,000 之间的数字,更大的值还是会被当作 40,000 来处理。
示例会确保当字段唯一值在 100 以内时会得到非常准确的结果。尽管算法是无法保证这点的,但如果基数在阈值以下,几乎总是 100% 正确的。高于阈值的基数会开始节省内存而牺牲准确度,同时也会对度量结果带入误差。
对于指定的阈值,HLL 的数据结构会大概使用precision_threshold * 8字节的内存,所以就必须在牺牲内存和获得额外的准确度间做平衡。
在实际应用中,100的阈值可以在唯一值为百万的情况下仍然将误差维持 5% 以内。
上面是官方文档的描述
实际上,我的请求测试,当文档很多的时候,这个误差还是很可观的:
Req
POST /xxx/_search
{
"size" : 0,
"aggs" : {
"distinc_count" : {
"cardinality" : {
"field" : "xx.keyword",
"precision_threshold": 100
}
}
}
}
Resp
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 2,
"successful": 2,
"failed": 0
},
"hits": {
"total": 570470,
"max_score": 0,
"hits": []
},
"aggregations": {
"distinc_count": {
"value": 11158
}
}
}
Req
POST /xxx/_search
{
"size" : 0,
"aggs" : {
"distinc_count" : {
"cardinality" : {
"field" : "xx.keyword",
"precision_threshold": 10000
}
}
}
}
Resp
{
"took": 3,
"timed_out": false,
"_shards": {
"total": 2,
"successful": 2,
"failed": 0
},
"hits": {
"total": 570470,
"max_score": 0,
"hits": []
},
"aggregations": {
"distinc_count": {
"value": 10736
}
}
}
提高了precision_threshold,得到高的精度估算精度,耗时当然也提高了点(因为之前请求过,所以有缓存,这么低的时延,正常应该几十毫秒)。精度到10000,就基本没误差了。