帮你快速理解、总结文档立即下载

Monkey Bloom Filter 调优

最近更新时间:2026-08-21 16:29:30
我的收藏
说明:
本文档中的数据描述,均来源于2026年腾讯内部实验测试结果,测试基于特定环境、条件及时间范围,仅反映相应测试场景下的情况,实际效果可能因业务场景、配置及使用情况不同而存在差异。

概述

Bloom Filter 是 TDStore RocksDB 存储层用于加速点查的概率型数据结构。读取一个 Key 时,Bloom Filter 可以快速判断该 Key 一定不在某个 SST 中,从而避免读取不必要的数据块。Bloom Filter 可能产生假阳性,但不会漏掉实际存在的 Key。
本特性是论文 Monkey: Optimal Navigable Key-Value Store 中 Bloom Filter 分层内存分配方法在 TDStore/RocksDB 上的工程实现。论文由 Niv Dayan、Manos Athanassoulis 和 Stratos Idreos 发表于 SIGMOD 2017,其核心思想是在给定内存预算下,为 LSM-tree 各层分配不同的 Bloom Filter 假阳性率,使各层假阳性率之和最小。TDStore 实现了其中与 Leveled Compaction Bloom Filter 优化相关的部分。
传统策略为每个 LSM-tree 层使用相同的 bits per key(BPK)。这种固定分配没有考虑不同层的数据量和访问代价,未必能在给定内存预算下取得最低的整体假阳性率。
Monkey Bloom Filter 根据 SST 所在层级分配不同的 BPK:为较小的上层分配更多过滤能力,为数据量更大的下层分配较少过滤能力。在平均内存预算不变的前提下,该策略可以降低各层累计的假阳性概率;也可以用更低的平均 BPK 达到与固定 BPK 相当或更好的过滤效果。
该特性应用于 TDStore 的 Leveled Compaction,主要优化点查以及写入过程中主键唯一性检查产生的点查。范围扫描不会直接受益于 Bloom Filter。

工作原理

固定 BPK 策略对所有层使用相同的 Bloom Filter 大小。由于 LSM-tree 各层的数据量通常呈几何级数增长,相同 BPK 会把绝大多数 Filter 内存分配给数据量最大的下层。
Monkey 将 BPK 作为整个 LSM-tree 的平均预算,再根据层数和层级大小比例计算每一层的目标假阳性率:
上层数据量较小,但一次假阳性会引入额外的 SST/Data Block 检查,因此分配更高的 BPK。
下层数据量较大,分配较低的 BPK,以控制 Filter 的总内存占用。
当某一层的最优结果是不构建 Filter 时,该层可以不生成 Filter。
SST 层级未知时,使用整体平均 BPK 作为回退值。

为什么 Monkey 能提升写入性能

Monkey 并不直接改变数据写入或 Compaction 流程。它对写入的收益来自写入前的主键存在性检查:
写入一个新的 Key
→ 检查主键是否已经存在
→ Key 不存在(negative point lookup)
→ Bloom Filter 排除不可能包含该 Key 的 SST
→ 减少假阳性引发的 SST/Data Block 检查
→ 降低写入延迟,提高写入 QPS
在本文测试使用的唯一 Key 插入负载中,每次写入都需要先检查主键是否已经存在;LCG 生成的 Key 保证不重复,因此这些检查基本都是 negative point lookup。不存在的 Key 无法通过找到目标记录提前结束查询,需要排查各层中可能包含该 Key 的 SST。此时,各层 Bloom Filter 假阳性率之和会直接影响额外的 SST 和 Data Block 检查次数。
Monkey 在相同平均内存预算下优化各层的 Bloom Filter 假阳性率分布,能够减少主键存在性检查中的无效读取。因此,negative point lookup 不仅是 Monkey 的核心读取受益场景,也是它能够提升新增 Key 写入性能的主要原因。随着数据量增加、LSM-tree 层数增多,这部分检查成本及优化收益通常会更加明显。
该收益取决于工作负载:更新已有 Key、写入重复 Key,或者跳过主键/唯一键存在性检查的写入路径,不一定属于 negative point lookup,也不一定获得同等收益。如果表中还有唯一二级索引,写入前的唯一性检查可能带来更多类似的点查。

测试效果

我们分别测试了传统固定分配(10 BPK)、Monkey 分层分配(10 BPK)和 Monkey 分层分配(7 BPK)。选择 Monkey 7 BPK 的目标不是单纯追求更低的理论假阳性率,而是在减少 Filter 内存占用的同时,保持或改善点查和写入性能。

大规模测试

大规模测试为三个对比策略分别准备规格相同、无其他负载干扰的独立环境。资源配置如下:
资源
配置
CPU
96核
实例内存
500GB
RocksDB Block Cache
约249GB(测试过程中实际容量)
数据盘
20TB,本地 SSD
Raft 盘
50GB,本地 SSD
日志盘
100GB,本地 SSD
磁盘带宽
原始测试记录的配置值与单位注释不一致,因此不将磁盘带宽作为本次结果的定量前提
写入和查询负载如下:
项目
写入阶段
查询阶段
Sysbench workload
oltp_insert(唯一 Key 版本)
oltp_point_select
并发线程
256
256
表数量
32
32
配置表容量
每表10亿行
每表10亿行
二级索引
Key 分布
使用 LCG 生成不重复的伪随机 Key
对已写入数据进行点查
测试时长
写入约2天,单组写入约4.9TB;整个实验按约6TB数据规模评估
600秒
LSM-tree 参数
使用 TDStore 默认参数
与写入阶段相同
最底层 Filter 策略
三组均不构建
三组均不构建
Monkey 7 BPK 相比传统固定10 BPK 的结果如下:
指标
固定 10 BPK
Monkey 10 BPK
Monkey 7 BPK
Monkey 7 相比固定 10
写入吞吐量(rows/s)
129,512
132,571
131,040
+1.18%
点查吞吐量(txn/s)
294,616
301,175
300,302
+1.9%
点查 P95 延迟(ms)
2.66
2.61
2.48
降低 6.8%
Filter Block Cache 占用
15.1 GB
15.8 GB
11.1 GB
降低 26.5%
各层实测 FPR 之和
7.728%
1.4%
5.32%
降低 31.2%
Filter 假阳性次数
15.212 亿
2.6082 亿
9.7315 亿
降低 36.0%
结果表明,Monkey 7 BPK 使用更少的 Filter 内存,同时没有以读写性能下降为代价。
该测试的写入收益与负载特征直接相关:LCG 保证写入 Key 不重复,因此写入前的主键存在性检查基本都是 negative point lookup,正好覆盖 Monkey 的核心优化场景。

资源受限测试

资源受限测试仍使用物理内存约500GB的测试机,但将 SQLEngine 可用内存限制为8GB,用于模拟小内存实例。三个对比策略分别运行在独立环境中。
资源
配置
CPU
SQLEngine 配额32核
物理机内存
约500GB
SQLEngine 内存限制
8GB
RocksDB Block Cache
2.70GB
数据盘配额
20TB
数据盘类型和限速
本次测试记录未单独记录
测试数据量
每组约1.5TB,测试结束时 LSM-tree 已增长到 L5
本节图表对应的写入负载如下:
项目
配置
Sysbench workload
oltp_insert(唯一 Key 版本)
并发线程
256
运行时长
约144,000秒(约40小时)
表数量和单表容量
本次测试记录未单独记录
LSM-tree 参数
三个对比组保持一致;本节原始记录未单独列出具体值
对比策略为:
传统固定分配(10 BPK)
Monkey 分层分配(10 BPK)
Monkey 分层分配(7 BPK)

写入 QPS


随着数据量和 LSM-tree 层数增长,Monkey 7 BPK 的优势逐渐扩大。测试记录显示:
累计写入吞吐量相比固定10 BPK 提升7.6%。
写入约1.5 TB时,瞬时 QPS 相比固定10 BPK 高约19%。
Monkey 10 BPK 在该资源配置下没有取得同等收益。
这一趋势也符合主键存在性检查的访问特征:LSM-tree 层数越多,negative point lookup 需要排查的候选层越多,Bloom Filter 假阳性带来的累计开销越高;Monkey 通过优化各层假阳性率分布来降低这部分开销。

写入 P95 延迟


在测试后半段,Monkey 7 BPK 的 P95 延迟明显低于固定10 BPK 和 Monkey 10 BPK。其主要原因不是 Monkey 7 BPK 产生了更少的 Data Block 访问,而是更小的 Filter 减少了 Filter Block 自身的缓存换入开销:
指标
固定 10 BPK
Monkey 10 BPK
Monkey 7 BPK
Data Block I/O 速率
1.99 GB/s
1.87 GB/s
2.38 GB/s
Filter Block I/O 速率
16.0 GB/s
16.9 GB/s
11.2 GB/s
总 Block I/O 速率
17.99 GB/s
18.77 GB/s
13.58 GB/s
Filter Block Cache 占比
82.0%
84.5%
78.0%
Monkey 7 BPK 将总 Block I/O 速率降低约24.5%,释放了更多 Block Cache 空间,因此在内存受限时取得更稳定的写入吞吐和延迟。
说明:
测试通过配置将 SQLEngine 内存限制为8GB,但物理机仍有约500GB内存。因此上述 Block I/O 可能主要由操作系统 Page Cache 承担,不能直接等同为物理磁盘 I/O。测试结果说明的是 Block Cache 受限时的相对变化,不代表所有硬件和负载下都能获得相同比例的性能提升。

优化价值

Monkey Bloom Filter 让 TDStore 能够根据 LSM-tree 的层级结构更有效地使用 Filter 内存,而不是简单地为所有层平均分配资源。测试结果体现了三方面价值:
更少的内存占用:在大规模测试中,Filter Block Cache 占用降低26.5%。
更高的读写性能:在减少 Filter 内存的同时,写入吞吐提升1.18%,点查吞吐提升1.9%,点查 P95 延迟降低6.8%。
资源受限时收益更明显:在8GB实例内存限制下,累计写入吞吐提升7.6%;写入约1.5TB时,瞬时 QPS 提升约19%。
这项优化尤其契合新增 Key 写入和不存在 Key 点查较多的负载。实际收益会随数据规模、LSM-tree 层数、Key 命中率和可用内存而变化。

参考资料