第
3 篇 提到 RocksDB 相对 LevelDB 的核心差异之一是
多线程 flush/compaction;第 11
篇 说明 compaction 跟不上时会
Stop/Delay
前台写。二者之间的旋钮是:后台 job
并发度(Env 线程池 +
max_background_*)与
磁盘写带宽上限(RateLimiter,默认
Mode::kWritesOnly)。
本文是系列 第 12 篇,源码锚点:RocksDB
9.x 的
db/compaction/compaction_job.h、db/db_impl/db_impl_compaction_flush.cc(MaybeScheduleFlushOrCompaction)、util/rate_limiter.cc、include/rocksdb/rate_limiter.h。令牌桶算法与
kWritesOnly 配置示例见 storage/67
写入性能优化 第六节——本篇写 内核调度挂载点与 I/O
优先级,不重复该文 MySQL/PG 对比段落。
版本锚定:RocksDB 9.x。实验:
reproduce/run_exp12.shexp12(RateLimiter 开/关,3 轮中位数;见 §七 本机片段)。
一、CompactionJob 生命周期与并发模型
单个自动或手动 compaction 对应一个
CompactionJob
实例(db/compaction/compaction_job.h
类注释):
Prepare() → Run() → Install()
(mutex) (无 mutex) (mutex)
| 阶段 | 职责 | 并发 |
|---|---|---|
| Prepare | 划分 subcompaction
边界(GenSubcompactionBoundaries) |
持 DB mutex |
| Run | 启动子任务线程,ProcessKeyValueCompaction,校验输出 SST | 释放 mutex;多 subcompaction 并行 |
| Install | LogAndApply 把新 SST 写入
Version,释放输入文件 |
持 DB mutex |
Subcompaction:大 compaction 按 key
范围切分为多个
SubcompactionState,各自
CompactionOutputs 写
SST;compaction_job_stats_ 聚合各子任务统计供
EventListener 使用。
并发上限受以下选项约束:
max_subcompactions:单 job 内并行 subcompaction 数;max_background_compactions/max_background_jobs:DB 级 同时运行的 CompactionJob 数;CompactionPriority::kRoundRobin等:可能AcquireSubcompactionResources向 Env 借额外线程(compaction_job.h私有方法注释)。
二、Env 线程池:Flush 与 Compaction 分离
DBImpl::MaybeScheduleFlushOrCompaction(db/db_impl/db_impl_compaction_flush.cc)在
持有 mutex 下循环
env_->Schedule:
flowchart LR
MS["MaybeScheduleFlushOrCompaction"] --> FL["HIGH pool: BGWorkFlush"]
MS --> FL2["LOW pool: BGWorkFlush<br/>(if HIGH empty)"]
MS --> CP["LOW pool: BGWorkCompaction"]
要点:
- Flush 优先使用
Env::Priority::HIGH后台池(max_background_flushes/max_background_jobs推导的max_flushes)。 - 若 HIGH 池线程数为 0,flush 降级 到 LOW 池(与 compaction 共享),避免无 flush 线程可用。
- Compaction 始终在 LOW
优先级池调度,直到
bg_compaction_scheduled_ + bg_bottom_compaction_scheduled_达到GetBGJobLimits().max_compactions且unscheduled_compactions_ > 0。
GetBGJobLimits(同文件):当仅设置
max_background_jobs 时,约
1/4 线程给 flush、其余给 compaction;若
write_controller_.NeedSpeedupCompaction()
为真(stall 压力),可 临时提高 compaction
并发(与第 11 篇
CompactionPressureToken 同族)。
三、RateLimiter:kWritesOnly 与 Request 路径
DB 级 Options::rate_limiter
默认由 NewGenericRateLimiter
构造(util/rate_limiter.cc),第四个参数
为 RateLimiter::Mode:
RateLimiter* NewGenericRateLimiter(
int64_t rate_bytes_per_sec,
int64_t refill_period_us /* = 100 * 1000 */,
int32_t fairness /* = 10 */,
RateLimiter::Mode mode /* = RateLimiter::Mode::kWritesOnly */,
bool auto_tuned /* = false */,
int64_t single_burst_bytes /* = 0 */);Mode::kWritesOnly(include/rocksdb/rate_limiter.h):仅对
写 I/O(flush、compaction 输出、部分 WAL
路径)调用
RateLimiter::Request;读
I/O 不计入配额。另有
kReadsOnly、kAllIo 等模式。
GenericRateLimiter
实现要点(util/rate_limiter.cc):
- 令牌桶:每
refill_period_us(默认 100ms)补充refill_bytes_per_period_; - 多优先级队列:
Env::IO_USER/IO_HIGH/IO_MID/IO_LOW;GeneratePriorityIterationOrderLocked用fairness随机化 HIGH 与 MID/LOW 的相对顺序; RequestToken:在IsRateLimited(op_type)为真时阻塞直到获得字节配额;Statistics 记NUMBER_RATE_LIMITER_DRAINS。
Compaction 写 SST 时使用
Env::IOPriority(CompactionJob::GetRateLimiterPriority):write
stall 期间可能 提高 compaction I/O
优先级,与前台写争用同一
kWritesOnly 桶。
四、与 Write Stall、DelayWrite 的三角关系
三类机制同时作用于 磁盘写带宽:
| 机制 | 作用对象 | 行为 |
|---|---|---|
| RateLimiter | 后台(及受控的前台)写 I/O | 硬 cap bytes/s;阻塞在 Request |
| DelayWrite | 前台 WriteImpl |
按 delayed_write_rate 主动
sleep |
| Stop token | 前台 WriteImpl |
完全等待 flush/compaction |
若 rate_limiter_bytes_per_sec
过小:
- Compaction 变慢 → L0 / pending bytes 上升 → Delay/Stop(第 11 篇);
- Flush 变慢 → immutable MemTable 堆积 → MemtableLimit stall。
若 不限速 但
max_background_compactions
过小:
- 并发 job 不足 → 同样触发 stall,但 磁盘可能打满(P99 抖动)。
工程上常在 shared nothing 多租户 或
Flink 与 compaction 同盘 时用 RateLimiter
保底;在 专用 NVMe
上更依赖 提高
max_background_jobs 与
compaction 优先级(详见 storage/67
6.2–6.3)。
4.1 文献与工业对照:并发 compaction 从哪来
| 来源 | 类型 | 本文引用点 |
|---|---|---|
| Sears & Ramanan, bLSM (SIGMOD 2012) | A 级论文 | 多核并行 compaction、控制 L0 run
数以缩短 stall;RocksDB 的 CompactionJob +
subcompaction 属同思路的工程演化 |
LevelDB BackgroundCompaction |
A 级源码 | 单线程 flush/compaction 互斥——bLSM 论文的对比基线(第 2 篇) |
| RocksDB Wiki, Thread Pool / Rate Limiter | A 级文档 | Env HIGH/LOW 池与 kWritesOnly
令牌桶语义 |
| Mark Callaghan, RocksDB Blog Write Stalls | B 级 | 生产 trace:compaction 并发不足时 stall 延长(现象线索) |
争论点:提高
max_background_jobs 能否 线性
缩短 compaction 债务?bLSM 与后续工业实践表明受
磁盘带宽、单 key range 依赖、subcompaction
切分 限制——RocksDB 在 stall 压力下用
NeedSpeedupCompaction()
临时加并发,不是无上限扩展。
4.2 工程间隙
| 论文/教程 | Flink / TiKV 生产 |
|---|---|
| 单机 SSD、均匀写 | checkpoint 读 SST + compaction 写 同盘 |
| RateLimiter 只限引擎内 I/O | cgroup / 云盘 IOPS 配额 在外层 再限一层 |
| 短 benchmark fillrandom | 长寿命 state + tombstone 使 compaction CPU 与 I/O 并重 |
五、CompactionJob::Run 与 I/O 统计
CompactionJob::Run(db/compaction/compaction_job.cc)在
subcompaction 线程中:
OpenCompactionOutputFile→ 经WritableFileWriter写块;- 写路径调用
RateLimiter::RequestToken(若 DB 配置了 limiter); RecordCompactionIOStats累加CompactionJobStats(读/写字节、耗时)。
Statistics tick
示例:COMPACTION_READ_BYTES、COMPACTION_WRITE_BYTES;与
写放大 估算相关(用户字节 vs compaction
写字节,口径见 storage/31)。
Manual compaction 与
CompactRange 仍走同一
CompactionJob 路径,但可能被
HasExclusiveManualCompaction
阻塞自动 picker
调度(MaybeScheduleFlushOrCompaction 内 early
return)。
六、关键 DB 选项对照
| 选项 | 默认语义(9.x 常见) | 内核效果 |
|---|---|---|
max_background_jobs |
与 flush/compaction 分配联动 | GetBGJobLimits |
max_background_flushes |
并行 FlushJob 上限 | HIGH 池 schedule 循环 |
max_background_compactions |
并行 CompactionJob 上限 | LOW 池 schedule 循环 |
max_subcompactions |
单 job 内并行度 | GenSubcompactionBoundaries |
rate_limiter + bytes/sec |
0 = 不限 | GenericRateLimiter |
delayed_write_rate |
stall 时目标写速率 | WriteController |
db_bench 暴露
--rate_limiter_bytes_per_sec(映射到
NewGenericRateLimiter),便于无代码实验。
七、实验 exp12:RateLimiter 对比方法论
reproduce/run_db_bench.sh
exp12 比较 无限速 vs
1 MiB/s 下 fillrandom
延迟(非吞吐结论文,而是
可复现口径):
cd post/db/rocksdb/reproduce
ROUNDS=3 ./run_db_bench.sh # 默认 ROUNDS=3
# 关注 output 中 Exp 12 段脚本逻辑(run_median 函数):
- 对每个配置
独立建库(
--use_existing_db=0,跑前rm -rfDB 目录); - 执行
fillrandom --num=100000,共ROUNDS次(默认 3); - 从 stdout 用
awk '/micros\/op/ {print $NF; exit}'提取每轮 micros/op; - 对三轮值 排序取中位数,打印
no_ratelimit median micros/op与ratelimit_1mib_s median micros/op。
有 RateLimiter 的一轮:
db_bench --benchmarks=fillrandom --num=100000 \
--rate_limiter_bytes_per_sec=1048576 --db=<path> --use_existing_db=0本机实测(WSL2,i9-12900K,RocksDB
9.4.0,2026-07-07;reproduce/run_exp12.sh,ROUNDS=3):
run 1: 1.808 micros/op run 2: 1.695 run 3: 1.703
no_ratelimit median micros/op: 1.703 (n=3)
run 1: 1.665 micros/op run 2: 1.542 run 3: 1.625
ratelimit_1mib_s median micros/op: 1.625 (n=3)
解读:在 100k
写、~0.18s/轮 的快盘上,1 MiB/s
kWritesOnly 限流几乎不改变 fillrandom
中位数——前台写路径与 WAL
批量落盘远未持续触顶;number.rate_limiter.drains
在 500k 写 长跑中亦可能仅个位数。exp12
的价值是 固定三轮中位数口径
与脚本可复现性,不是证明「1 MiB/s 必拖慢
10×」。要观察 limiter 与 stall 联动,需 更长
--num、更低
bytes/sec,或配合 iostat 看
flush/compaction 写带宽是否贴顶。
可选扩展:固定
--threads=1、绑定
CPU、--statistics=1 观察
NUMBER_RATE_LIMITER_DRAINS 与 第 11
篇 stall tick。
7.1 开放问题
- RateLimiter vs cgroup blkio:两层限速叠加时 tail 由谁主导?无统一论文;生产常 二选一 或 limiter 设略高于 cgroup 配额。
- Remote compaction(RocksDB
产品方向):compaction 写 I/O
移出进程后,
kWritesOnly语义是否仍覆盖 Region 本地 WAL?版本以 release note 为准(db-frontier/14)。 - Subcompaction 公平性:skew key range 下并行 subcompaction 能否让 热点层 先完成?社区 issue 讨论多,无闭式最优调度。
八、与相邻篇目分工
| 话题 | 本篇 | 他处 |
|---|---|---|
| Write stall 状态机 | 已衔接 | 第 11 篇 |
| Leveled picker | 不展开 | 第 10 篇 |
| Column Family 独立 LSM / 共享 limiter | 不展开 | 第 13 篇 |
| cgroup I/O 限速 | 不展开 | storage/67 |
九、小结
CompactionJob通过 subcompaction 在 Run 阶段并行;DB 级并发由MaybeScheduleFlushOrCompaction与 Env HIGH/LOW 池 调度。RateLimiter::kWritesOnly默认只限 写路径;与DelayWrite、Stop token 形成 带宽硬顶 + 背压软顶 两层控制。- bLSM(SIGMOD 2012)提供 多核
compaction 减 stall 的学术锚点;RocksDB
CompactionJob+ 线程池是工程实现,非论文逐行复现。 - exp12 三轮中位数 1.703 vs 1.625
micros/op(快盘短跑)——说明 limiter 效果须
拉长负载或降低配额 才可见;脚本见
reproduce/run_exp12.sh。
上一篇:Universal、FIFO 与 Write Stall
下一篇:Column Family
返回 系列目录
参考资料
- RocksDB 9.x
源码:
db/compaction/compaction_job.h、db/compaction/compaction_job.cc;db/db_impl/db_impl_compaction_flush.cc(MaybeScheduleFlushOrCompaction、GetBGJobLimits);util/rate_limiter.cc;include/rocksdb/rate_limiter.h(A 级)。 - RocksDB Wiki, Thread Pool / Rate Limiter / Compaction(A 级)。
- Sears & Ramanan, bLSM: A General Purpose Log Structured Merge Tree, SIGMOD 2012(多核 compaction;A 级)。
- storage/67
写入性能优化(RateLimiter
配置、
kWritesOnly、fairness;本篇引用不重复全文)。 - storage/32
RocksDB 实践(
max_background_jobs等生产模板)。 - 本机
exp12:
reproduce/output/exp12_ratelimit.txt(median 1.703 vs 1.625 micros/op,2026-07-07)。 - 第 3、11 篇 架构 diff 与 write stall 背景。
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【RocksDB 内核机制】RocksDB 架构演进:相对 LevelDB 的 diff 地图
对照 LevelDB 1.23,用 diff 表与源码路径梳理 RocksDB 9.x 的多线程 flush/compaction、Column Family、RateLimiter、Direct IO、Statistics 与并发 MemTable 写,建立后续读 db_impl 子文件的坐标系。
【RocksDB 内核机制】LSM 生态全景:存储引擎三角与 RocksDB 生态位
闭合存储引擎三角的最后一角:对照 PG B-Tree 与列存 scan,定位 LSM 写优化引擎在 TiKV、Flink、Kafka Streams、ClickHouse Embedded 中的嵌入方式,并划分本系列与 lsm-tree DIY、storage/31–32、stream/12 的分工边界。
【RocksDB 内核机制】WAL 与 WriteBatch:持久化与原子批写
从 log::Writer 的 32KB Block 分片 record、WriteBatch 二进制布局,到 DBImpl::WriteImpl 中 Group Commit 与 sync/fsync 语义,钉住写路径第一步:先 WAL 后 MemTable 的提交顺序与原子性边界。
【RocksDB 内核机制】MemTable 与 Flush:SkipList 到 L0 SST
从 SkipList MemTableRep、MemTableList 的 immutable 双缓冲队列,到 FlushJob::WriteLevel0Table 与 VersionSet 安装 L0 文件,闭合 WAL 之后 MemTable 如何变成 SST,并对照 lsm-tree DIY 实现。