HyperLogLog内存占用与基数非线性:小基数用稀疏模式(几百字节),超阈值(默认约3000唯一元素)后跃升至12KB稠密模式;精度参数hll-precision需重启生效,影响内存与误差。

HyperLogLog内存占用和基数的关系不是线性的
Redis 的 HLL 底层用的是稀疏(sparse)→ 稠密(dense)双模式存储,小基数时用稀疏编码(0x01~0xFF前缀+变长整数),内存极省;一旦超过阈值(默认约 3000 个唯一元素),自动转为 12KB 固定大小的稠密结构。这意味着:从 100 到 2999 个不同值,内存可能只占几百字节;但第 3000 个一插入,立刻跳到 12KB —— 这个跃变点必须心里有数。
- 稀疏模式下,实际内存 ≈
16 + 8 × 实际非零寄存器数字节(寄存器最多 2^13 = 8192 个,但稀疏模式只存非零项) - 稠密模式固定占用
12500字节(即 12KB 左右),与实际基数无关 - 触发转换的阈值由
hll_sparse_max_bytes配置控制,默认是3000,但这个值不是“元素个数”,而是稀疏编码估算的内存上限(单位字节)
调整 hll_precision 会影响精度和内存,但 Redis 不支持运行时修改
Redis 启动时通过 --hll-precision N 或配置项 hll-precision 设置精度参数 m(寄存器数量 = 2^m),默认 m=14 → 16384 个寄存器 → 稠密模式固定 12KB。改小 m 能直接减内存,比如设为 12(4096 寄存器),稠密结构降到约 3125 字节;但代价是标准误差从 0.81% 升到 3.2%,且小基数下更易触发稀疏→稠密误判。
-
m=10→ 寄存器 1024 个 → 稠密结构约780字节,误差 ~12.8% -
m=16→ 65536 寄存器 → 稠密结构约48KB,误差 ~0.2%,极少用 - 注意:
hll-precision是启动参数,修改后必须重启 Redis 实例,已有HLL数据不会重算,新写入才生效 - 如果只是临时压测或灰度,建议在新实例上验证,别在线上直接改配置重启
PFCOUNT 返回值不准?先确认是否真超了稀疏模式容量
当 PFCOUNT 返回值明显偏低(比如插入 5000 个不同值却只报 2000),大概率是稀疏模式已满、但尚未完成向稠密结构的转换(尤其在老版本 Redis ≤ 3.2 中存在竞态 bug)。这不是精度问题,而是内部状态不一致。
- 用
DEBUG OBJECT key查看实际编码:返回中含encoding: hll且serializedlength接近 12500 是稠密;若远小于(如 200),则是稀疏 - 执行一次
PFADD key ""(空字符串)可强制触发转换,但会轻微扰动结果(空值也算一个元素) - 升级到 Redis ≥ 4.0 可规避多数稀疏/稠密切换异常,4.0+ 默认启用更稳定的稀疏编码逻辑
线上评估 HLL 内存不能只看 MEMORY USAGE
MEMORY USAGE 返回的是整个 key 的内存开销,包含 key 名、过期信息、dictEntry 等额外结构,对 HLL 来说往往比纯数据大 2–3 倍。真正要盯的是稠密结构是否被大量创建,以及它们是否长期闲置。
- 用
SCAN+DEBUG OBJECT批量检查所有HLLkey 的serializedlength,统计> 12000的占比 - 如果大量 key 都卡在 3000 左右基数、反复横跳于稀疏/稠密之间,说明业务场景本身基数波动剧烈,不如直接用
m=12启动,统一走小内存稠密模式 - 定期
PFADD大量重复值不会增加内存(HLL 本身去重),但频繁调用仍消耗 CPU;避免在循环里对同一 key 反复PFADD千次以上
稀疏模式的边界行为、精度参数的不可热更、以及 MEMORY USAGE 的误导性,这三点最容易在压测或扩容时突然冒出来。











