原文发布于 quant67.com,转载请保留出处。
第 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 类注释):
text
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:
要点:
- 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:
cpp
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 延迟(非吞吐结论文 ,而是 可复现口径):
bash
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 的一轮:
bash
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):
text
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 背景。