RocksDB Universal 与 Write Stall:L0 积压怎么触发 DelayWrite

原文发布于 quant67.com,转载请保留出处。

第 10 篇Leveled 如何通过层级 score 与 LevelCompactionPicker 控制 L0 与 Size Ratio。生产上还有 Universal (写放大更友好)、FIFO (队列 / 时序数据)、TTL (过期淘汰)等策略;无论哪种 style,当前台写入快于 flush + compaction 时,都会进入同一套 Write Stall 机制:WriteController 对全 DB StopDelayDBImpl::DelayWrite 阻塞或降速 WriteImpl

本文是系列 第 11 篇 ,源码锚点:RocksDB 9.xdb/column_family.ccGetWriteStallConditionAndCauseRecalculateWriteStallConditions)、db/write_controller.hdb/db_impl/db_impl_write.ccDelayWrite)、db/compaction/compaction_picker_universal.ccdb/compaction/compaction_picker_fifo.cc。通用 write stall 背景见 storage/67 写入性能优化 第七节------本篇写 内核状态机与 LOG/Property 入口

版本锚定 :RocksDB 9.xlevel0_slowdown_writes_trigger / level0_stop_writes_trigger 默认值随版本可能调整,以 options/cf_options.hSanitizeOptions 为准。实验:reproduce/run_db_bench.sh exp11


一、三种 Compaction Style 在内核中的位置

ColumnFamilyData 构造 compaction picker 时分支(db/column_family.cc):

compaction_style Picker 类 典型场景
kCompactionStyleLevel LevelCompactionPicker 默认;点查多、可接受较高 WA
kCompactionStyleUniversal UniversalCompactionPicker 写密集;减少层间反复合并
kCompactionStyleFIFO FIFOCompactionPicker 仅追加、按时间淘汰;常配合 TTL

TTL 不是独立 style,而是 CF 选项 ttl(秒):ComputeCompactionScore 与 picker 会把 超过 TTL 的 SST 纳入 ExpiredTtlFiles(),触发 compaction 或(FIFO 下)删除。Universal 下 SanitizeOptions 会把 periodic_compaction_secondsttl 对齐处理(见 column_family.cc 注释)。

Universal 与 Leveled 的分工差异

  • Score :Universal 在 ComputeCompactionScore 中把 L1+ 非空层 也计入 L0 的 sorted run 数,whole-DB 更像「多层 run 归并」而非严格 L(i)→L(i+1)。
  • Write stall 阈值 :仍共用 level0_slowdown_writes_trigger / level0_stop_writes_trigger (对 Universal 而言语义是 run 数,不仅是物理 L0 文件数)。
  • 读放大 :Universal 通常 run 数少、单层文件更大 ,RA 与 SA 的折中与 Leveled 不同(公式级讨论见 storage/31,本篇不展开)。

FIFO 与 TTL

FIFO 按 文件时间顺序 淘汰:当总大小超过 compaction_options_fifo.max_table_files_size 时删除最老文件;若 allow_compaction 为 true,score 也会参考 L0 文件数。SanitizeOptions 要求 FIFO + TTL 时常设 max_open_files = -1,否则 open 文件数限制与按序删除冲突。


二、Write Stall 触发条件(CF 级)

ColumnFamilyData::GetWriteStallConditionAndCausedb/column_family.cc)在 持有 DB mutex 的路径上,根据当前 SuperVersion 统计返回 (WriteStallCondition, WriteStallCause)

stateDiagram-v2 direction TB [*] --> Normal Normal --> Delayed: L0 slowdown / soft pending / memtable-1 Normal --> Stopped: L0 stop / hard pending / memtable full Delayed --> Stopped: 条件恶化 Stopped --> Delayed: compaction 消化 debt Delayed --> Normal: debt 下降 Stopped --> Normal: 恢复

Stopped(WriteStallCondition::kStopped --- 对应 WriteController::GetStopToken()

条件 Cause 枚举 典型阈值选项
未 flush MemTable 数 ≥ max_write_buffer_number kMemtableLimit 默认 2--4
L0 文件数(或 Universal run 数)≥ level0_stop_writes_trigger kL0FileCountLimit 默认 36(可调)
estimated_compaction_needed_byteshard_pending_compaction_bytes_limit kPendingCompactionBytes 默认 256 GiB 量级

Delayed(WriteStallCondition::kDelayed --- 对应 GetDelayToken(rate)

条件 Cause
MemTable 数 ≥ max_write_buffer_number - 1(且 >3 等前提) kMemtableLimit
L0 数 ≥ level0_slowdown_writes_trigger kL0FileCountLimit
pending bytes ≥ soft_pending_compaction_bytes_limit kPendingCompactionBytes

SanitizeOptions 强制 level0_stop_writes_triggerlevel0_slowdown_writes_triggerlevel0_file_num_compaction_trigger,避免阈值倒挂。

2.1 Write stall 决策直觉(Leveled vs Universal)

把 stall 想成 三级背压 ,与 第 10 篇 §4.1 的 score 链衔接:

flowchart LR W["Write / Flush"] --> L0["L0 sorted runs ↑"] L0 --> T{"runs ≥ compaction_trigger?"} T -->|yes| CP["Schedule compaction"] L0 --> D{"runs ≥ slowdown?"} D -->|yes| DEL["DelayWrite + GetDelay"] L0 --> S{"runs ≥ stop?"} S -->|yes| STP["StopWrite bg_cv_.Wait"] CP --> L0 DEL --> W STP --> W
旋钮 作用 写路径表现
level0_file_num_compaction_trigger score ≥1\ge 1 ≥1,调度 compaction 仍全速写;后台 job 增多
level0_slowdown_writes_trigger Delay token 每次 Put 可能 sleep;STALL_MICROS 上升
level0_stop_writes_trigger Stop token 写线程阻塞至 flush/compaction 安装新 SuperVersion
soft/hard_pending_compaction_bytes_limit pending 字节过大 同 Delay/Stop,cause 为 kPendingCompactionBytes
max_write_buffer_number immutable memtable 堆积 MemTable cause;与 flush 线程数相关(第 5 篇

Leveled vs Universal 差异 (Dayan & Idreos, Merge Bush SIGMOD 2017 → Universal picker):Leveled 的 stall 计数主要看 物理 L0 文件数 ;Universal 把 L1+ 非空层 也计入 sorted run(§一)------相同阈值数字 下 Universal 更易进入 Delay/Stop,但单次 compaction 吞 run 的方式不同,不能用同一套 trigger 直接横比策略优劣。

策略争论锚点Merge Bush 主张 tiering 降 WAWA WA;Dostoevsky (SIGMOD 2018)说明固定预算下 leveled/tiering 皆非全局最优------RocksDB 默认 Leveled + 可选 Universal 是工程折中,不是论文最优解的移植。选型 POC 见 第 18 篇

延迟数字 :本篇 不给出 stall 毫秒级 benchmark;本机 exp11 在 WSL2 快盘上 stall.micros=0(§七)。线上 tail 须结合 LOG、rocksdb.is-write-stopped 与磁盘带宽自测。


三、WriteController:DB 级 Stop 与 Delay

多个 Column Family 共享 一个 WriteController(挂在 ColumnFamilySet)。Token 语义(db/write_controller.h):

  • StopWriteTokentotal_stopped_++;存在任一 stop token 时 IsStopped() 为真,所有 CF 写路径等待
  • DelayWriteTokentotal_delayed_++NeedsDelay() 为真时,每次写调用 GetDelay(clock, num_bytes) 返回应 sleep 的微秒数
  • CompactionPressureToken :compaction 落后时 NeedSpeedupCompaction() 为真,可提高后台 compaction 并发上限(与第 12 篇线程池联动)。

delayed_write_rate 默认由 MutableDBOptions::delayed_write_rate 注入(常见默认 64 MiB/s 量级);SetupDelaycolumn_family.cc 匿名命名空间)在 已 delay 时根据 estimated_compaction_needed_bytes 增减速率(kIncSlowdownRatio / kDecSlowdownRatio),接近 stop 时乘以 kNearStopSlowdownRatio 进一步降速。


四、RecalculateWriteStallConditions 与 LOG

每次安装新 SuperVersion (flush / compaction 完成、LogAndApply 后),ColumnFamilyData::InstallSuperVersion 调用 RecalculateWriteStallConditions

  1. 调用 GetWriteStallConditionAndCause
  2. 释放旧 token,按新状态申请 StopDelay token;
  3. ROCKS_LOG_WARN 并累加 InternalStats CF 计数器。

L0 stop 典型 LOG(源码原文模式):

text 复制代码
[<cf>] Stopping writes because we have <N> level-0 files

L0 delay 典型 LOG:

text 复制代码
[<cf>] Stalling writes because we have <N> level-0 files rate <bytes/sec>

MemTable stop:

text 复制代码
Stopping writes because we have <N> immutable memtables ... max_write_buffer_number is set to <M>

若 stall 发生但 已有 L0 compaction 在进行 ,stats 会额外记 L0_FILE_COUNT_LIMIT_*_WITH_ONGOING_COMPACTION,用于区分「compaction 已努力但仍跟不上」与「compaction 未调度」。


五、DBImpl::DelayWrite:写路径上的状态机

Leader write thread 在 WriteImpl 中若检测到 write_controller_.IsStopped()NeedsDelay() ,进入 DBImpl::DelayWritedb/db_impl/db_impl_write.cc):

  1. Delay 阶段 :主队列调用 GetDelay;若 WriteOptions::no_slowdown,返回 Status::Incomplete("Write stall") 而不睡眠。
  2. 否则 BeginWriteStall释放 DB mutex ,按 delay 微秒 sleep (循环中每 ~1ms 检查是否仍 NeedsDelay)。
  3. Stop 阶段 :若 IsStopped() ,在 bg_cv_Wait (mutex 释放),直到 compaction/flush 安装新 SuperVersion 并 SignalAll
  4. 累计 STALL_MICROSWRITE_STALL histogram、kIntStatsWriteStallMicros
sequenceDiagram participant WT as WriteThread leader participant WC as WriteController participant BG as Flush/Compaction WT->>WC: IsStopped / NeedsDelay? alt Delay WT->>WC: GetDelay(num_bytes) WT->>WT: Sleep(delay_us) end alt Stop WT->>WT: bg_cv_.Wait() BG->>WT: InstallSuperVersion SignalAll end WT->>WT: WriteImpl continues

这与 storage/67 写停顿流程图 一致,但 sleep + condition variable 的细节以 db_impl_write.cc::DelayWrite 为准。

5.1 文献与工业对照:stall 是反馈控制,不是「bug」

Write stall 在文献与工业界的定位是 背压(backpressure),不是实现失误:

来源 类型 观点
RocksDB Wiki, Write Stalls A 级文档 L0 / memtable / pending bytes 超阈值 → 故意 降速或停写,给 compaction 时间
Sears & Ramanan, bLSM (SIGMOD 2012) A 级论文 Many-core compaction + 控制 L0 run 数,减少 stall 持续时间;RocksDB 多线程 compaction 属同思路的工程演化
Mark Callaghan, RocksDB Blog Write Stalls 系列 B 级 生产 trace 中 stall 与 I/O 饱和、小 memtable 的关联(作现象线索,不单独支撑数值)
Flink 运维文档 B 级 state 膨胀时 checkpoint 变长 常与 RocksDB L0/compaction 叠加(stream/13

争论点 :更激进的 write throttle (早 delay、晚 stop)能否在 不牺牲读 SLA 下降低 tail?论文侧无统一答案;RocksDB 用 level0_slowdown_writes_triggerSetupDelay自适应降速 是工程折中,不是最优控制理论解。

5.2 工程间隙

论文/教程 生产(Flink/TiKV)
单 CF、均匀写 多 CF、KeyGroup 倾斜
stall 仅影响 Put 延迟 叠加 checkpoint 读 SST、JVM GC
长时间 steady-state 突发 barrier、Region 迁移

六、GetProperty 与 db_bench Statistics

生产判读常用接口(详见 storage/67 7.3 节):

cpp 复制代码
db->GetProperty("rocksdb.is-write-stopped", &value);
db->GetProperty("rocksdb.actual-delayed-write-rate", &value);
db->GetProperty("rocksdb.num-immutable-mem-table", &value);
db->GetProperty("rocksdb.compaction-pending", &value);
db->GetProperty("rocksdb.level0-slowdown-writes-trigger", &value);
db->GetProperty("rocksdb.level0-stop-writes-trigger", &value);

打开 Statistics 时关注 tick(include/rocksdb/statistics.h):

Tick / 计数 含义
STALL_MICROS 写路径 stall 总微秒
NUMBER_BLOCKING_FLUSHES / MEMTABLE 相关 flush 背压
CF stats:L0_FILE_COUNT_LIMIT_DELAYS / STOPS L0 触发的 delay/stop 次数

db_bench --statistics=1 结束时会打印 Stalls(count) 行(见 storage/32 示例格式),例如 level0_slowdownlevel0_numfiles 等分类计数------具体数字须本机运行得到


七、实验 exp11:刻意触发 L0 stall

reproduce/run_db_bench.sh exp11极低 L0 阈值 在 fillrandom 中 试图 放大 stall:

bash 复制代码
db_bench --benchmarks=fillrandom --num=500000 --value_size=256 \
  --level0_slowdown_writes_trigger=2 \
  --level0_stop_writes_trigger=4 \
  --statistics=1 --db=<path>

本机实测 (WSL2,i9-12900K,RocksDB 9.4.0,2026-07-07;以下经删减):

text 复制代码
fillrandom   :       2.901 micros/op 344660 ops/sec 1.451 seconds 500000 operations
rocksdb.stall.micros COUNT : 0
rocksdb.db.write.stall ... COUNT : 0

解读 :尽管 slowdown=2stop=4,在 高速 SSD + 1.45s 跑完 500k 写 的条件下,可能仍观察不到 stall ------L0 文件数未持续超过阈值,或 compaction 足够快。这 不否定 第五节状态机;只说明 exp11 是机制验证脚本,不是保证触发 stall 的 benchmark 。要稳定看到 stall,需:更长 --duration更慢磁盘 、或 人为 pause compaction(仅实验环境)。

注意 :阈值过低的配置 仅用于实验 ;生产见 storage/32第 17 篇

7.1 开放问题

  1. 跨 CF 公平 stall :多 CF 共享 WriteController 时,单 CF L0 爆炸是否过度拖累全局?社区讨论见 RocksDB GitHub issues;无单一论文结论
  2. Predictive throttle :能否用 estimated_compaction_needed_bytes 训练提前降速,避免 hard stop?研究线与 AutoTune 实验相关,生产默认仍以阈值为主。
  3. Universal vs Leveled stall 语义 :Universal 把 L1+ run 计入 L0 score------同样阈值下 stall 频率不同 ;跨策略比较须固定 workload(第 10 篇 Dostoevsky 框架)。

八、与相邻篇目分工

话题 本篇 他处
Leveled score / Intra-L0 / §4.1 score→stall 链 不展开 第 10 篇
toy 引擎 compaction 策略与最小堆归并 不展开 lsm-tree 第 4 篇 --- 无 WriteController,但 L0 文件数→读退化同源
Flink TM 上 stall 表现为 checkpoint/sync 变长 现象 + 阈值 stream/12 嵌入路径;stream/13 调优
RateLimiter 与 compaction 并发 不展开 第 12 篇
Universal 放大公式 引用 storage/31
生产 LOG / GetProperty 排查 不展开 第 17 篇

分工一句话 :lsm-tree 教 归并结构与策略 ;rocksdb 10--12 教 RocksDB 9.x 状态机 ;stream/12 教 Flink 如何把 stall 叠在 barrier/checkpoint 上


九、小结

  • Universal / FIFO / TTL 改变 picker 与 score ,但 write stall 仍由 GetWriteStallConditionAndCause 统一判定,经 WriteController token 作用于全 DB。
  • level0_slowdown_writes_triggerDelay (可变速率);level0_stop_writes_triggerStopbg_cv_ 硬等)。
  • RecalculateWriteStallConditions 连接 compaction 进度与 LOG / InternalStatsDelayWrite 是写线程侧的 sleep + wait 实现。
  • GetPropertyStatistics 是线上判读 stall 的一手入口;exp11 在快盘上 可能 stall=0,须延长负载或换磁盘再验证。
  • Stall 是 背压机制 (Wiki A 级);bLSM(SIGMOD 2012)代表 多核 compaction 减 stall 的学术线,RocksDB 实现以源码为准。

上一篇Leveled Compaction

下一篇并发 Compaction 与 Rate Limiter

返回 系列目录


参考资料

源码与 Wiki(A 级)

  1. RocksDB 9.xdb/column_family.ccdb/write_controller.hdb/db_impl/db_impl_write.cc
  2. RocksDB Wiki, Write Stalls / Universal Compaction / FIFO Compaction

论文(A 级)

  1. Sears & Ramanan, bLSM: A General Purpose Log Structured Merge Tree , SIGMOD 2012 --- 多核 compaction 与 stall 持续时间。
  2. Dayan & Idreos, The Log-Structured Merge-Bush , SIGMOD 2017Dostoevsky , SIGMOD 2018 --- Universal vs Leveled 定量框架。

本站相关

  1. storage/67storage/32
  2. 第 10、12、17 篇lsm-tree 第 4 篇stream/12--13

实验(A 级,本机)

  1. exp11(§七):WSL2 + RocksDB 9.4.0,stall.micros=0 --- 作为「stall 很轻」的泛化结论,仅证明短跑/快盘可能未触阈。
相关推荐
ltl18 分钟前
CockroachDB 对照:Range + Raft 的另一种 HTAP 落地
数据库
ltl18 分钟前
PostgreSQL Buffer Manager:Clock sweep 与 shared_buffers 边界
数据库
迷枫7121 小时前
SQL性能排查记录
java·数据库·sql
Elastic 中国社区官方博客8 小时前
搜索倍增器:推动收入、生产力和 AI 实现规模化
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索
保定公民10 小时前
达梦数据库存储过程中的数组类型详解:基础数组与记录数组的差异化应用
数据库·达梦·存储过程·达梦数据库·dm8·dm
梦Arrebol11 小时前
Redis 内容及相关实验
数据库·redis
大模型码小白11 小时前
Spring AI Tool 实现自然语言操作 MySQL 数据库详解
服务器·开发语言·数据库·人工智能·python·mysql·spring
DBA大董13 小时前
TDengine3.0 DBA常用的运维命令和SQL2
运维·数据库·时序数据库·dba·tdengine