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

【RocksDB 内核机制】并发 Compaction 与 Rate Limiter

文章导航

分类入口
databasestorage
标签入口
#rocksdb#compaction-job#subcompaction#env-thread-pool#rate-limiter#kWritesOnly#max-background-jobs#background-compaction

目录

第 3 篇 提到 RocksDB 相对 LevelDB 的核心差异之一是 多线程 flush/compaction第 11 篇 说明 compaction 跟不上时会 Stop/Delay 前台写。二者之间的旋钮是:后台 job 并发度Env 线程池 + max_background_*)与 磁盘写带宽上限RateLimiter,默认 Mode::kWritesOnly)。

本文是系列 第 12 篇,源码锚点:RocksDB 9.xdb/compaction/compaction_job.hdb/db_impl/db_impl_compaction_flush.ccMaybeScheduleFlushOrCompaction)、util/rate_limiter.ccinclude/rocksdb/rate_limiter.h。令牌桶算法与 kWritesOnly 配置示例见 storage/67 写入性能优化 第六节——本篇写 内核调度挂载点与 I/O 优先级,不重复该文 MySQL/PG 对比段落。

版本锚定:RocksDB 9.x。实验:reproduce/run_exp12.sh exp12(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 使用。

并发上限受以下选项约束:


二、Env 线程池:Flush 与 Compaction 分离

DBImpl::MaybeScheduleFlushOrCompactiondb/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"]

要点:

  1. Flush 优先使用 Env::Priority::HIGH 后台池(max_background_flushes / max_background_jobs 推导的 max_flushes)。
  2. 若 HIGH 池线程数为 0,flush 降级 到 LOW 池(与 compaction 共享),避免无 flush 线程可用。
  3. Compaction 始终在 LOW 优先级池调度,直到 bg_compaction_scheduled_ + bg_bottom_compaction_scheduled_ 达到 GetBGJobLimits().max_compactionsunscheduled_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::kWritesOnlyinclude/rocksdb/rate_limiter.h):仅对 写 I/O(flush、compaction 输出、部分 WAL 路径)调用 RateLimiter::Request读 I/O 不计入配额。另有 kReadsOnlykAllIo 等模式。

GenericRateLimiter 实现要点(util/rate_limiter.cc):

Compaction 写 SST 时使用 Env::IOPriorityCompactionJob::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 过小

不限速max_background_compactions 过小

工程上常在 shared nothing 多租户Flink 与 compaction 同盘 时用 RateLimiter 保底;在 专用 NVMe 上更依赖 提高 max_background_jobscompaction 优先级(详见 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::Rundb/compaction/compaction_job.cc)在 subcompaction 线程中:

  1. OpenCompactionOutputFile → 经 WritableFileWriter 写块;
  2. 写路径调用 RateLimiter::RequestToken(若 DB 配置了 limiter);
  3. RecordCompactionIOStats 累加 CompactionJobStats(读/写字节、耗时)。

Statistics tick 示例:COMPACTION_READ_BYTESCOMPACTION_WRITE_BYTES;与 写放大 估算相关(用户字节 vs compaction 写字节,口径见 storage/31)。

Manual compactionCompactRange 仍走同一 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 函数):

  1. 对每个配置 独立建库--use_existing_db=0,跑前 rm -rf DB 目录);
  2. 执行 fillrandom --num=100000,共 ROUNDS 次(默认 3);
  3. 从 stdout 用 awk '/micros\/op/ {print $NF; exit}' 提取每轮 micros/op
  4. 对三轮值 排序取中位数,打印 no_ratelimit median micros/opratelimit_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.shROUNDS=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.drains500k 写 长跑中亦可能仅个位数。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 开放问题

  1. RateLimiter vs cgroup blkio:两层限速叠加时 tail 由谁主导?无统一论文;生产常 二选一 或 limiter 设略高于 cgroup 配额。
  2. Remote compaction(RocksDB 产品方向):compaction 写 I/O 移出进程后,kWritesOnly 语义是否仍覆盖 Region 本地 WAL?版本以 release note 为准(db-frontier/14)。
  3. Subcompaction 公平性:skew key range 下并行 subcompaction 能否让 热点层 先完成?社区 issue 讨论多,无闭式最优调度

八、与相邻篇目分工

话题 本篇 他处
Write stall 状态机 已衔接 第 11 篇
Leveled picker 不展开 第 10 篇
Column Family 独立 LSM / 共享 limiter 不展开 第 13 篇
cgroup I/O 限速 不展开 storage/67

九、小结


上一篇Universal、FIFO 与 Write Stall

下一篇Column Family

返回 系列目录


参考资料

  1. RocksDB 9.x 源码:db/compaction/compaction_job.hdb/compaction/compaction_job.ccdb/db_impl/db_impl_compaction_flush.ccMaybeScheduleFlushOrCompactionGetBGJobLimits);util/rate_limiter.ccinclude/rocksdb/rate_limiter.h(A 级)。
  2. RocksDB Wiki, Thread Pool / Rate Limiter / Compaction(A 级)。
  3. Sears & Ramanan, bLSM: A General Purpose Log Structured Merge Tree, SIGMOD 2012(多核 compaction;A 级)。
  4. storage/67 写入性能优化(RateLimiter 配置、kWritesOnly、fairness;本篇引用不重复全文)。
  5. storage/32 RocksDB 实践max_background_jobs 等生产模板)。
  6. 本机 exp12:reproduce/output/exp12_ratelimit.txt(median 1.703 vs 1.625 micros/op,2026-07-07)。
  7. 第 3、11 篇 架构 diff 与 write stall 背景。

同主题继续阅读

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


By .