土法炼钢兴趣小组的算法知识备份

【RocksDB 内核机制】Block Cache 与 Bloom:读放大裁剪

文章导航

分类入口
databasestorage
标签入口
#rocksdb#block-cache#table-cache#bloom-filter#ribbon-filter#partitioned-index#read-amplification

目录

第 7 篇GetImpl 在 MemTable 未命中后会进入 Version::GetTableCache::Get,对每个候选 SST 读取 Index、Filter、Data Block。第 8 篇 的范围扫描在同一 Table Cache 上打开更多 InternalIterator。两层缓存——Table Cache(缓存已打开的 TableReader)与 Block Cache(缓存 SST 内的块)——加上 Bloom / Ribbon FilterPartitioned Index,共同决定 LSM 点查的 实际读放大

本文是系列 第 9 篇,源码锚点:RocksDB 9.xdb/table_cache.cctable/block_based/block_based_table_reader.*table/block_based/full_filter_block.cctable/block_based/partitioned_index_reader.cc。Bloom 误判率公式、Block Cache 容量规划、LRU vs HyperClockCache 对比等 通用读优化推导storage/68 读取性能优化——本篇只写 RocksDB 内核中的挂载点与 CF 选项边界,不重复该文表格与 benchmark 口径。

版本锚定:RocksDB 9.x。FPR 公式与 BPK 对照表引用 storage/68(A/B 级),不在此重抄。


一、读放大在 Get 路径上的落点

结合 storage/68 第一节 的 LSM 点查示意图,RocksDB 生产路径可细化为:

flowchart TB
  GET["Version::Get / TableCache::Get"] --> OPEN{"TableReader 在 Table Cache?"}
  OPEN -->|miss| OPENFILE["Open SST + 读 Footer"]
  OPEN -->|hit| FILTER["FilterBlock::KeyMayMatch?"]
  OPENFILE --> FILTER
  FILTER -->|否| SKIP["跳过 Data Block I/O"]
  FILTER -->|是/无 Filter| INDEX["Index Block 定位 Data Block"]
  INDEX --> BC{"Block Cache 命中?"}
  BC -->|hit| DECODE["解压 + 块内二分"]
  BC -->|miss| DISK["Read + Insert Block Cache"]
  DISK --> DECODE

字节读放大I/O 读放大 在 storage/68 已定义:LSM 层数深、L0 文件多、Bloom 误判、Index 过大都会推高分子。本篇关注 RocksDB 如何用 Cache 与 Filter 压低分子

组件 缓存对象 未命中代价
Table Cache TableReader + 文件描述符 打开 SST、读 Footer、建 Index/Filter 句柄
Block Cache Index / Filter / Data / Compression Dict 等 一次或多次 RandomAccessFile::Read
Bloom / Ribbon 通常作为 Filter Block 进入 Block Cache 误判时多读 Data Block

二、Table Cache:SST 文件级缓存

TableCachedb/table_cache.hdb/table_cache.cc)按 (column_family_id, file_number) 缓存 已打开的 TableReader

FindTable 流程概要:

  1. cache_.Lookup(key) — 命中则直接返回 TableReader*
  2. 未命中:GetTableReader 打开磁盘 SST,解析 Footer,构造 BlockBasedTable
  3. cache_.Insert(key, table_reader, charge, handle) — 插入 Table Cache。

TableCache::Get(点查)与 NewIterator(扫描)在持有 TableReader 后,调用 BlockBasedTable::Get / NewIterator,进入块级读取。

与 Block Cache 的分工

Table Cache 大小由 max_open_files 与 LRU 驱逐间接控制;文件句柄耗尽时会 关闭冷文件 并从 Table Cache 剔除,下次读重新 open(TableCache 注释:error 结果不缓存,便于修复后恢复)。


三、Block Cache:块级缓存与优先级

Block Cache 接口在 rocksdb/cache.h;默认实现为 分片 LRUHyperClockCache(8.x+,高并发友好)。配置挂在 BlockBasedTableOptions::block_cacheDBOptions::row_cache(行级,另议)。

BlockBasedTableReader 读块时(table/block_based/block_based_table_reader.cc):

  1. (cache_key) 查 Block Cache。
  2. 命中:解压(若启用 compression)并解析。
  3. 未命中:从文件读取 → 可选 decompress → Insert 进 Cache

缓存键 与文件 identity、块 offset、块类型相关;RocksDB ≥ 6.24 对同 DB 内 cache key 更稳定(见 HISTORY 中 block cache key 说明)。

高优先级池

Index Block 与 Filter Block 可标记 高优先级cache_priority::HIGH),Data Block 为 LOW。配合 Cache::SetHighPriorityPoolRatio(storage/68 第四节示例),减少 「Data 挤掉 Index/Filter」 导致的读放大尖刺。这是工程上常见的 点查 P99 优化

pin_l0_index_and_filter_blocks

BlockBasedTableOptions::pin_l0_index_and_filter_blocks 等选项把 L0 的 index/filter 固定在内存,适合 L0 文件多、点查多的 workload;代价是 内存占用上升,需与 Block Cache 总容量一并规划(storage/68 容量规划一节;本篇不重复命中率–延迟算术)。


四、Bloom Filter 与 Ribbon Filter

作用位置

每个 SST 的 Filter Block(Meta Block)回答:KeyMayMatch(user_key)

FullFilterBlockReader::KeyMayMatchtable/block_based/full_filter_block.cc)在 whole_key_filtering 下对整 key 查询;有 prefix_extractor 时可同时维护 prefix 与 whole key(AddKeyAndAlt)。

Full Filter vs Block-based Filter(历史)

RocksDB 7.0 起默认倾向 Full Filter(整文件一个 filter block),较旧的 block-based filter(每 Data Block 一个 filter 子块)在大文件、高 BPK 下 I/O 次优。生产配置应显式:

rocksdb::BlockBasedTableOptions table_options;
table_options.filter_policy.reset(rocksdb::NewBloomFilterPolicy(10, false));
// 第二个参数 false = 非 block-based,即 full filter 路径

bits_per_key 与理论 FPR 的关系见 storage/68 第三节 公式与 BPK 表;默认 10 bits/key0.82% FPR(该文引用数据,不在此重算)。

Ribbon Filter

NewRibbonFilterPolicyutil/bloom_test.ccinclude/rocksdb/filter_policy.h)用 Ribbon(近似 Bloom 构造) 在相同 FPR 下 更省空间更低 CPU(RocksDB Blog, Ribbon Filter,B 级)。db_bench--use_ribbon_filter 可切换(tools/db_bench_tool.cc)。

适用边界:

optimize_filters_for_hits

假设 几乎总是命中 时,可对 最大层 跳过 filter 构建以省空间与 compaction CPU;若 workload 含大量 Get 不存在 key,会 显著增加 无效 Data Block 读(storage/68 已警告;默认慎用)。

4.1 文献谱系:从 Bloom 到 Ribbon

工作 年份 / 出处 本文引用点
Burton Bloom, Space/Time Trade-offs in Hash Coding Communications of the ACM 1970 假阳性、无假阴性 的 membership test 起源
Kirsch & Mitzenmacher, Less Hashing, Same Performance Random Structures & Algorithms 2006 双 hash 构造 Bloom 的等价性(工程实现常用)
LSM 读放大 O’Neil 1996;Dayan & Idreos SIGMOD 2017–18 sorted run 数 决定 probe 次数;Filter 剪枝 块级 I/O第 10 篇
RocksDB Ribbon Filter RocksDB Blog(B 级) 同 FPR 下 更省空间/CPU 的 filter 构造;NewRibbonFilterPolicy

争论边界:提高 bits_per_key 降低 FPR,但 增大 SST 与 compaction CPU——与 RUM / 三种放大中 用空间换读 同构(第 1 篇 §2.4)。Learned indexdb-frontier/04)在 静态 workload 下可 beat Bloom,但 更新与 crash 一致 仍是开放问题——不能 默认替代生产 Bloom/Ribbon。

4.2 工程间隙

论文 / storage/68 公式 TiKV / Flink 嵌入
均匀随机 Get KeyGroup 热点 → 少数 SST 过热
单机 Block Cache Flink managed memory 与 Write Buffer 分池
固定 BPK changelog 恢复阶段 全表 scan 使 cache 失效

4.3 开放问题

  1. Ribbon 默认化:新版 RocksDB 是否应在 低 QPS 嵌入 场景默认 Ribbon?取决于 compaction CPU 与读 SLA,无 universal 答案
  2. Filter 与 disaggregated cache:远程 Block Cache 下 filter 是否仍本地 pin?产品与 CXL 论文方向并存。
  3. Range tombstone + Bloom:Bloom 对 tombstone 不剪枝;长 scan 读放大仍由 第 8 篇 MergeIterator 决定。

五、Partitioned Index 与 Filter

当 SST 达到 GB 级,单体 Index Block 可能达数十 MB,一次 cache miss 成本高。Partitioned Index 把 Index 切成多个 partition,由 Top-Level Index 指向(table/block_based/partitioned_index_reader.cc)。

启用方式(与 storage/68 一致,此处只列内核相关项):

table_options.index_type =
    rocksdb::BlockBasedTableOptions::kTwoLevelIndexSearch;
table_options.metadata_block_size = 4096;  // partition 目标大小
table_options.partition_filters = true;    // Filter 也可分区
table_options.pin_top_level_index_and_filter = true;

读路径变化

  1. 先读 Top-Level Index(常 pin 在内存)→ 定位 Index Partition。
  2. 读对应 Index Partition → 定位 Data Block。
  3. Filter 分区时同理:Top-Level FilterFilter PartitionMayMatch

pin_top_level_index_and_filter = true 保证点查最多 多一次 partition I/O,而不是加载整棵巨型 Index。Partitioned Filter 与 Ribbon 可叠加,用于 宽表、大 key 空间(TiKV 等场景常见)。


六、与 GetImpl / Version::Get 的衔接

Version::Get 循环中(db/version_set.cc):

*status = table_cache_->Get(
    read_options, *internal_comparator(), *f->file_metadata, ikey,
    &get_context, mutable_cf_options_, file_read_hist, skip_filters, level, ...);

skip_filtersVersion::IsFilterSkipped(level, ...) 决定——例如 L0 最后文件已知 key 必存在 的优化路径、或 ReadOptions 层指示。Filter 跳过后 直接 Index/Data,读放大上升但延迟可能降低(极特殊场景)。

GetContext::MarkKeyMayExist vs SaveValue:Bloom 可能误判 时仍进入块内查找,最终 kNotFound;Statistics 中 BLOOM_FILTER_USEFUL(filter 帮跳过)与 BLOOM_FILTER_FULL_POSITIVE 等 tick 可观测 filter 效果(需打开 Statistics)。

Block Cache 追踪:BLOCK_CACHE_HIT / BLOCK_CACHE_MISSBLOCK_CACHE_INDEX_HIT 等(详见 storage/68 GetTickerCount 示例,本篇不重复代码块)。


七、调参边界(不展开 storage/32 cookbook)

目标 首选旋钮 内核侧注意
降低不存在 key 的 I/O 提高 BPK / Ribbon compaction 构建 filter 更慢、SST 更大
降低存在 key 的 Index I/O Partitioned Index + pin top 内存 pin 与 cache 重复计费
提高 cache 命中 增大 Block Cache、HIGH priority 给 index/filter 与 MemTable / OS page cache 争内存
降低 open 开销 提高 max_open_files 进程 fd 与 Table Cache 压力
Scan 友好 较大 block_size、iterator readahead 点查可能多读无关 Data

200+ 参数模板与 db_bench 填参见 storage/32 RocksDB 实践三种放大 定义见 storage/31lsm-tree 全景


八、与相邻篇目分工

话题 本篇 他处
FPR 公式、BPK 表、Cache 命中率–延迟 引用 storage/68
Get / Iterator 路径 已写 第 7–8 篇
Compaction 降低 L0 / 层数 不展开 第 10–11 篇
Flink state cache 现象 不展开 stream/12、第 16 篇

九、小结


上一篇Iterator 与 MergeIterator

下一篇Leveled Compaction

返回 系列目录


参考资料

  1. RocksDB 9.x 源码:db/table_cache.cctable/block_based/block_based_table_reader.cctable/block_based/full_filter_block.cctable/block_based/partitioned_index_reader.ccinclude/rocksdb/filter_policy.h(A 级)。
  2. RocksDB Wiki, Block Cache / Bloom Filter / Index Block(A 级)。
  3. Burton Bloom, Space/Time Trade-offs in Hash Coding, CACM 1970(Bloom filter 奠基;A 级)。
  4. Kirsch, Adam & Mitzenmacher, Michael, Less Hashing, Same Performance, RSA 2006(A 级)。
  5. storage/68 读取性能优化(Bloom 公式、Block Cache 架构、Partitioned Index 配置;本篇引用不重复推导)。
  6. storage/31 LSM 调参storage/32 RocksDB 实践(调参边界)。
  7. 第 1 篇 §2.4 RUM第 7–8 篇 Get/Iterator 路径。
  8. db-frontier/04 Learned Index(与 Bloom 争论边界;B 级接口)。

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-07-07 · database / storage

【RocksDB 内核机制】经典故障与排查

用 GetProperty 与 LOG 钉住 L0 堆积、compaction 落后、Block Cache 未命中与 ENOSPC write stall 四类生产故障;交叉引用 storage/76 磁盘耗尽链,给出 db_bench 复现步骤而不伪造 stats 数字。


By .