说明:
本文档中的数据描述,均来源于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 命中率和可用内存而变化。