Count-Min Sketch:海量数据频率统计的“轻量级计数器“

Count-Min Sketch:从论文原理到工程落地的"轻量级计数器"

一句话总结:Count-Min Sketch(CMS)是一种用固定内存空间近似统计流数据频率的概率数据结构。它用空间换精度,用确定性上界换亚线性内存,是处理海量实时计数的工程利器。


一、论文出处

Count-Min Sketch 由 Graham CormodeS. Muthukrishnan 于 2005 年正式提出,发表在 Journal of Algorithms 上:

Cormode, G., & Muthukrishnan, S. (2005). "An improved data stream summary: the count-min sketch and its applications." Journal of Algorithms , 55(1), 58--75.

DOI: 10.1016/j.jalgor.2003.12.001

该论文奠定了 CMS 的理论基础,并系统论证了其在数据流摘要、频率估计、Heavy Hitter 检测等场景中的应用。此后 CMS 成为流计算、网络遥测、数据库近似查询等领域的标准工具之一 。

  • 使用一个 d × w d \times w d×w 的计数器矩阵;
  • 对每个元素用 d d d 个独立哈希函数映射到每行的一个计数器;
  • 更新 时:所有映射位置 + 1 +1 +1;
  • 查询 时:取 d d d 个位置中的最小值作为频率估计。

由于哈希碰撞只会使计数器"高估"(overestimate),取最小值可在概率意义上将误差控制在 ε ∥ a ∥ 1 \varepsilon \|a\|_1 ε∥a∥1 以内,空间复杂度仅为 O ( ( 1 / ε ) log ⁡ ( 1 / δ ) ) O((1/\varepsilon) \log(1/\delta)) O((1/ε)log(1/δ))。


二、核心原理:为什么"取最小值"就能估计频率?

2.1 数据结构

CMS 本质上是一个 d × w 的二维计数器数组(整数矩阵),外加 d 个两两独立的哈希函数:

参数 含义 工程建议
w (width) 每行桶的数量 决定误差范围,通常 1K~100K
d (depth) 哈希函数个数 / 行数 决定置信度,通常 4~8
h₁...h_d 哈希函数 要求两两独立,可用 MD5/xxHash 取模

2.2 更新操作(Add)

当元素 x 到达时,对每一行 i 计算 idx = h_i(x) % w,并将对应桶 table[i][idx] 加 1:

python 复制代码
for i in range(d):
    idx = hash_i(x) % w
    table[i][idx] += 1

关键特性 :不同元素可能哈希到同一桶(碰撞),导致计数器被"共享"和高估

2.3 查询操作(Estimate)

查询 x 的频率时,同样计算 d 个哈希位置,返回 d 个桶中的最小值

python 复制代码
return min(table[i][hash_i(x) % w] for i in range(d))

为什么取 min?

  • 每个桶的计数 = 真实计数 + 碰撞噪声(来自其他元素)
  • 噪声 ≥ 0,因此每个桶都是真实值的上界
  • d 个上界的最小值,相当于选择"噪声最少"的那个估计,从而最接近真实值

误差保证 :对于任意元素,估计值 f̂(x) 满足 f(x) ≤ f̂( x) ≤ f(x) + ε·N,其中 ε = e/wN 为总流量。超过该上界的概率不超过 δ = e^(-d)


三、应用场景

CMS 的"固定内存 + 单向更新"特性使其在以下场景大放异彩:

场景 用途
网络流量监控 实时统计每个 IP/五元组的包数/字节数,检测 Heavy Hitter
数据库近似查询 GROUP BY 计数、频率直方图、Top-K 预筛选
缓存系统 追踪键的访问频率,辅助 LRU/LFU 淘汰策略
日志分析 实时统计 URL 访问、错误码出现次数
推荐系统 用户行为流中 item 的曝光/点击计数
分布式系统 各节点本地维护 CMS,定期合并做全局频率估计

四、优点与局限

4.1 优点

  1. 内存固定 :空间复杂度 O(w·d),与元素种类数无关。统计 1 亿个键和 1 千个键占用同样内存。
  2. 更新 O(d):常数时间,无哈希表扩容问题。
  3. 可合并:两个 CMS 对应桶相加即可合并,天然适合分布式并行。
  4. 确定性上界:永远不会低估,适合需要保守估计的场景(如限流、配额检查)。

4.2 局限

  1. 只高估,不低估:所有误差都是正的,不适合需要精确下界的场景。
  2. 无法枚举 keys:只能"点查"已知 key,无法列出所有高频 key(需配合 Heap 做 Top-K)。
  3. 不支持删除 :标准 CMS 只有 +1,没有 -1(可用 Count Sketch 或带符号 CMS 解决)。

五、工程进阶:访问文件统计 + 周期性衰减

在实际生产环境中,在文件访问统计这类典型时序场景中,直接使用标准 CMS 会遇到两个核心问题:

历史累积污染(Historical Accumulation)

  • 标准 CMS 的计数器只增不减。若一个文件在上周是热点,本周已无人问津,其历史高计数仍会长期占据 Top-K 列表,导致热点漂移检测严重滞后

缺乏时间局部性感知

  • 文件访问具有明显的时间局部性(Temporal Locality):最近被访问的文件在未来更可能被再次访问。标准 CMS 对所有历史访问一视同仁,无法反映"近期热度"。

我们需要:

  1. 周期性重置:防止计数器无限增长,定期清零重新统计
  2. 指数衰减:让历史数据"降温",新数据更有话语权
  3. 多级衰减策略:日衰减(温和)+ 周衰减(激进),适应不同业务周期

5.1 设计思路

复制代码
┌─────────────────────────────────────────┐
│         DecayingCountMinSketch          │
├─────────────────────────────────────────┤
│  CMS Core (width × depth 计数器矩阵)     │
├─────────────────────────────────────────┤
│  Time-based Controller                  │
│  ├── 每 N 小时: 全量 Reset              │
│  ├── 每 24 小时: Daily Decay (×0.9)     │
│  ├── 每 7 天:   Weekly Decay (×0.5)     │
│  └── 每 M 分钟: Interval Decay (平滑)   │
└─────────────────────────────────────────┘

衰减公式count_new = count_old × factor(factor ∈ (0,1))

  • 日衰减 0.9:一天前的访问权重降为 90%,两天为 81%,三天为 72.9%...
  • 周衰减 0.5:一周前的权重直接腰斩,快速淘汰旧热点

5.2 完整代码实现

以下代码实现了 基础 CMS + 带时间衰减的包装器 + 文件访问统计 Demo

下载完整代码

核心类设计:

python 复制代码
class CountMinSketch:
    """基础 CMS:d × w 计数器矩阵 + d 个哈希函数"""
    def __init__(self, width=2000, depth=5, seed=42):
        self.table = [[0] * width for _ in range(depth)]
        # ...

    def add(self, item, count=1):
        for i in range(self.depth):
            idx = self._hash(item, i)
            self.table[i][idx] += count

    def estimate(self, item):
        return min(self.table[i][self._hash(item, i)] for i in range(self.depth))

    def decay(self, factor):   # 整表缩放
        for row in self.table:
            for j in range(len(row)):
                row[j] = int(row[j] * factor)


class DecayingCountMinSketch:
    """带时间衰减的 CMS 包装器"""
    def __init__(self, width=2000, depth=5,
                 reset_interval_hours=24,
                 decay_interval_minutes=60,
                 daily_decay_factor=0.9,
                 weekly_decay_factor=0.7):
        self.cms = CountMinSketch(width, depth)
        self.reset_interval = timedelta(hours=reset_interval_hours)
        self.daily_decay_factor = daily_decay_factor
        self.weekly_decay_factor = weekly_decay_factor
        # 时间戳追踪:last_reset / last_daily_decay / last_weekly_decay

    def _check_and_apply_decay(self):
        now = datetime.now()
        # 优先级:Reset > Weekly > Daily > Interval
        if now - self.last_reset >= self.reset_interval:
            self.cms.reset()
        elif now - self.last_weekly_decay >= timedelta(days=7):
            self.cms.decay(self.weekly_decay_factor)
        elif now - self.last_daily_decay >= timedelta(days=1):
            self.cms.decay(self.daily_decay_factor)
        elif now - self.last_decay >= self.decay_interval:
            factor = self.daily_decay_factor ** (hours_passed / 24)
            self.cms.decay(factor)

5.3 运行演示

Demo 1:文件访问统计

模拟 1000 次文件访问(80% 命中 3 个热点文件),CMS 仅用 16 KB 内存完成统计:

复制代码
文件路径                                          估计访问次数    实际近似
----------------------------------------------------------------------
/home/user/cache/temp_001.bin                            16         16
/home/user/config/app.yml                               305        305
/home/user/src/main.py                                  282        282
/var/log/system.log                                     275        275
...

所有文件的估计值与真实值完全一致(在小数据量下碰撞概率极低)。不存在的文件查询返回 0,展示了良好的特异性。

Demo 2:衰减效果对比

阶段 普通 CMS 衰减 CMS 说明
初始 500 次 500 500 无差别
日衰减 ×0.9 500 450 历史降温
周衰减 ×0.7 500 315 旧数据快速淘汰
新增 100 次 600 415 新数据占比提升

Demo 3:内存对比

Width Depth CMS 内存 精确哈希表(100万键)
1,000 4 16 KB ~58 MB
10,000 4 160 KB ~58 MB

CMS 的内存占用与数据量完全解耦 ,只取决于 width × depth


六、参数调优建议

目标 调参方向
降低内存 减小 width,但会增大误差上界 ε = e/w
提高精度 增大 width,或配合 Bloom Filter 做冷 key 过滤
提高置信度 增大 depth,降低 δ = e^(-d)
更快适应变化 提高衰减系数(如 0.8),缩短 reset 周期
平滑统计 降低衰减系数(如 0.95),延长 decay 间隔

七、总结

Count-Min Sketch 是一种"用确定性上界换取亚线性空间"的优雅数据结构。在工程落地中,单纯的原版 CMS 往往不够------通过引入周期性重置多级指数衰减,我们可以在固定内存内实现"有记忆但会遗忘"的流式统计,完美适配文件访问监控、缓存热度追踪、API 限流等生产场景。

核心口诀:宽行容误差,深行保置信;哈希定位置,取小得估计;衰减控记忆,重置开新局。


参考

  • Cormode, G., & Muthukrishnan, S. (2005). An improved data stream summary: the count-min sketch and its applications. Journal of Algorithms, 55(1), 58--75.
  • Cormode, G. (2011). Sketch techniques for approximate query processing. Foundations and Trends in Databases. NOW publishers.
  • Huang, Q., et al. (2021). Toward nearly-zero-error sketching via compressive sensing. NSDI 21, 1027--1044.
  • Cao, Y., Feng, Y., & Xie, X. (2023). Meta-sketch: A neural data structure for estimating item frequencies of data streams. AAAI 2023, 6916--6924.
  • Cidon, A., et al. (2017). Memshare: a dynamic multi-tenant key-value
    cache. USENIX ATC 17, 321--334.
相关推荐
yiruwanlu2 小时前
特色美食赋能乡村文旅设计:经典案例落地路径深度解析
大数据·人工智能·python
王老师青少年编程2 小时前
csp信奥赛C++高频考点专项训练之贪心算法 --【排序贪心】:加工生产调度
c++·算法·贪心·csp·信奥赛·排序贪心·加工生产调度
在屏幕前出油2 小时前
08. ORM——快速开始
数据库·后端·python·sql·pycharm·orm
捉鸭子2 小时前
某红书X-s X-s-common VMP逆向(算法还原)
python·web安全·网络安全·node.js·网络爬虫
三毛的二哥2 小时前
BEV:MapTR
人工智能·算法·计算机视觉·3d
qq_366740602 小时前
《多模态大模型——算法、应用与微调》勘误
python
小菜鸡桃蛋狗2 小时前
C++——vector
开发语言·c++·算法
黎阳之光2 小时前
黎阳之光:以视频孪生硬核实力,抢抓交通科技新机遇
大数据·人工智能·算法·安全·数字孪生
噜噜噜阿鲁~2 小时前
python学习笔记 | 7.2、高级特性-迭代
笔记·python·学习