# WriteOptions - **sync**: bool,写时调用 `WritableFile::Sync()` 即 `fdatasync()` - **dsiableWAL**:不写 WAL,默认 false - **ignore_missing_column_families**:写入到不存在的 cf 时,忽略不返回错误。默认 false。 - **no_slowdown**:不 slowdown,取而代之返回 Status::Incomplete 当写阻塞时,默认 false - **low_pri**:标记当前写请求优先级为低,如果 compaction 有压力时,会被 slow down。 - 写入时会调用 `DBImpl::ThrottleLowPriWritesIfNeeded()` - **memtable_insert_hint_per_batch**: - 仅对 `concurrent_memtable_writes` 为 true 时有效 - 在当前 batch 内维护每个 memtable 最后的写入位置,当 batch 内的 keys 为顺序性时,可以提升 concurrent write 的性能。 - **rate_limiter_priority**:请求 rate limiter 时的优先级 --- # WriteBatch - **`rep_`**:`std::string`,batch 的数据都保存在这里 | sequnce | count | record 0 | ... | record N | | ------- | ------- | -------- | --- | -------- | | fixed64 | fixed32 | | | | - record 的编码根据 record 的类型(`ValueType`)而不同 - record 里使用 user key,seq 使用 batch header 里的。 ## WriteBatchWithIndex - 对 WriteBatch 的封装,增加一个跳表索引,可以搜索某个 key 在 rep 中的 offset。 - 设计目的:支持 Mongo 的 read-its-own-write. -------- # WriteThread - db/write_thread.h - db/write_thread.cc ## State enum - 表示 Writer 当前的状态 - 枚举值: - **`STATE_INIT`**:writer 创建后的初始状态 - **`STATE_GROUP_LEADER`**:writer 变为 leader - **`STATE_COMPLETED`**:写入完成 - 对于 follwer:自己的写入已经完成 - 对于 leader:包括所有的 followers 也完成 - **`STATE_PARALLEL_MEMTABLE_WRITER`**:leader 唤醒 followers 去并行写 memtable - `DBOptions::allow_concurrent_memtable_write` 为 true 时有效 - **`STATE_PARALLEL_MEMTABLE_CALLER`**:并行唤醒优化 - follwers 去唤醒其他 follower writers 写 memtable,然后自己再写 memtable - leader 先唤醒部分 writers,这些 writers 再**并行**唤醒其他 writers - [Optimizations in notify-one #12545](https://github.com/facebook/rocksdb/pull/12545) - **`STATE_LOCKED_WAITING`**: 有线程在阻塞等待 state ,state 有变化后需要使用 cond 通知。 - **`STATE_MEMTABLE_WRITER_LEADER`**:用于 Pipeline write ## Writer - 每次写入操作都会构建一个 Writer - 核心成员: - **`batch`**:`WriteBatch`,该 writer 写入的数据 - **`write_group`**:`WriteGroup *`, writer 所属的 WriteGroup - **`state_mutex`**:`std::mutex`,保护 state 成员 - **`state_cv`**:`std::condition_variable`, - 可能有线程等待 Writer state 的变化,cv 用于通知 - **`state`** :`atomic<uint8_t>` - writer 的运行状态,取值范围来自 State 枚举的组合 - **`link_older`**:`Writer *` - **`link_newer`**:`Writer *`,writers 队列的链表指针,newer 指向后入队列的 writer - **`user_write_cb`**:`UserWriteCallback`,写操作入列和写完 WAL 后调用回调。[#12603](https://github.com/facebook/rocksdb/pull/12603/) - **`log_used`**:`uint64_t` ,batch 写入到哪个个 WAL 文件中(编号) ## WriteGroup - **`leader`**:`Writer *`, group 的 leader writer(起点) - **`last_writer`**:`Writer *`,group 的最后一个 writer,跟 `leader` 组成一个 group 链表 - **`last_sequence`**:`SequenceNumber` - **`status`**:`Status`,l eader 写入执行的结果 - **`running`**:`atomic<size_t>`, 未完成的 writers 数量 - **`size`**:`size_t`, group 内 writers 的个数 ## WriteThread 成员 - **`allow_concurrent_memtable_write_`**:`bool`, 来自 `DBOptions`,默认为 true - **`max_write_batch_group_size_bytes`**:`uint64_t`,来自 `DBOptions`,默认 1 MB - 一个写入批次的最大字节数限制。 <br> - **`newest_writer_`**:`atomic<Writer *>`,指向最新的 pending writer,链表头 - 只有 leader 可以删除元素,任意人可以无锁添加 - **`last_sequence_`**:`SequenceNumber`,被 writer 消费的 last seq。 - 不一定是读可见的,因为写入可能还在进行中。 <br> - **`write_stall_dummy_`**:`Writer`,leader 在队列末尾插入 dummy 表示需要 write stall,新 writers 到来后进行检查。 - **`stall_mu_`** - **`stall_cv`**:用于发生 write stall 时进行阻塞。 ```mermaid --- title: writers 链表(队列) --- flowchart RL newest_writer_ -- link_order --> writer_2 writer_2 -- link_order --> writer_1 ``` ## WriteThread 函数 ### AwaitState - 入参 - **`w`**:`Writer` - **`goal_mask`**: `uint8_t`,State 枚举值的组合 - **`ctx`**: `AdaptationContext`, - 作用:等待 w 的 state 满足 `(state & goal_mask) != 0`,即满足任一枚举值 - 等待流程(adaptive): 1. 使用 pause 指令通过 busy loop 循环检测 200 次(总计 1 微秒多) 2. 使用 `std::this_thread::yield()` 循环等待 ( yield 延迟优于 futex) - 只有 `DBOptions::enable_write_thread_adaptive_yield` 为 true 才启用 - 最多等待 `DBOptions::write_thread_max_yield_usec` 3. 进入阻塞等待(调用基于 mutex 和 cond 的 BlockingAwaitState) ### BlockingAwaitState - 阻塞等待 Writer w 满足 `goal_mask` - 流程 - 初始化该 Writer 的 state mutex 和 cv - waker 检测到下面设置的 WAITING 状态才会使用 cv,确保了已经被初始化 - 将 state 通过 CAS 设置为 `STATE_LOCKED_WAITING` - 如果 CAS 失败,则表示 state 变化了,目标也满足了。 - 等待 cond,条件为 state 不等于 `STATE_LOCKED_WAITING` - 即有人调用 SetState,取消了 WAITING 标记 ### SetState - 将 Writer w 的 state 设置为 new_state - 如果 state 为 `STATE_LOCKED_WAITING`,则表示有线程在等待 state,通知 cv 唤醒 - [x] SetState 和 Wait 分别可能有几个线程调用 ### CreateMissingNewerLinks() - 跟随 `link_older` 指针遍历链表,初始化 `link_newer` 指针 ### LinkOne() - 入参 - **`w`**:`Writer` - **`newest_writer`**: `atomic<Writer*> *`,指向 `newest_writer_`的指针 - 作用:将 w 链接到 `newest_writer` 链表(放入队列),支持多线程并发调用(CAS)。 - 将 w 设置为 `newest_writer`(即将 w 插入到链表头) 1. load `newest_writer` 到 `writers` 变量 - 如果链表头 `writers` 为 `&write_stall_dummay_`,进入 slowdown 流程 2. `w->link_order = writers` // w 指向之前的 `newest_writer` 3. `newest_writer->cas(writers, w)` - 如果 `writers` 为 nullptr,则表示之前的链表为空,新插入的 w 就是 leader,函数返回 true。 - slowdown 处理 - 如果 w 设置了 `no_slowdown` 标记,设置 status 为 `Incomplete` 后直接返回 - 否则,等待 `stall_cv_` 通知,直到 `newest_writer`不再为 stall dummy。 ### JoinBatchGroup(Writer* w) - 将 w 放入队列,等待 w 的 state 满足条件可以进行后续工作。 1. 调用 LinkOne 函数,调用结果为 `linked_as_leader` 2. 检测 `linked_as_leader` 结果 - 如果 `linked_as_leader` 为 true(`newest_writer_`为空时),表明 w 成为 leader - 设置它的 state 为 `STATE_GROUP_LEADER`,返回 - 如果 `lined_as_leader` 为 false, 调用 AwaitState 等待 state 变为如下之一: - `STATE_GROUP_LEADER` :现有的 leader 完成后,挑选我们作为新的 leader - `STATE_COMPLETED`:leader 已经代我们写入完成 - `STATE_PARALLEL_MEMTABLE_WRITER` | `STATE_PARALLEL_MEMTABLE_CALLER` - leader 通知 followers 去写 memtable ### EnterAsBatchGroupLeader() - 由 leader 构造一个新的 WriteGroup - 入参: - **`leader`**: `Writer` - **`write_group`**:`WriteGroup *`,在该变量内构造 group - 返回值:`size_t`,group 的 bytes 总和 - 遍历 leader 到 `newest_writer` 链表区间 的 writers - 从链表移除与 leader 不兼容的 writers 放到一个新链表,最后统一插入到原队尾(write group 的后面) [#12138](https://github.com/facebook/rocksdb/pull/12138) - 例如 sync,`no_slowdown`, `disable_wal` 等选项与 leader 不同的 - writer 的 size 加上当前 group 累积的 size 超出单 group 最大限制的 - 避免大 group - 满足条件的 writer 加入到 group 内 - 一个 write group 的大小限制: - `DB::max_write_batch_group_size_bytes`,默认 1MB - 如果 leader 写入的 batch 很小,则会进一步限制 group 的大小,防止拖慢 small write - 默认如果 leader 小于 128 KB,则 group 限制到 `leader_write_size + 128KB` - PR: [Option to make write group size configurable #5759](https://github.com/facebook/rocksdb/pull/5759) ### ExitAsBatchGroupLeader() - group 写入完成后的收尾工作 - 如果是并行写 Memtable,则需要等待所有 memtable 都写入完成后才能执行该步骤。 - 入参: - **`write_group`**:`WriteGroup&`,目标 group - **`status`**:`Status`, - 如果 `write_group` 之后有新的 writer,提升它为新的 leader(设置 state 为 `STATE_GROUP_LEADER`),并将 `write_group` 从队列中移除 - 遍历 `write_group`,设置每个 writer 的 status,以及设置状态为 `STATE_COMPLETED` ### ExitAsBatchGroupFollower - 允许并行写 memtable时,如果最后一个完成写 memtable 的是某个 follower,则由它代替 leader 调用 ExitAsBatchGroupLeader 完成收尾工作 - 执行流程: 1. 调用 ExitAsBatchGroupLeader() 2. 设置 leader 的 state 为 `STATE_COMPLETED` - leader 调用 `CompleteParallelMemTableWriter`,在等待 `STATE_COMPLETED` ### LaunchParallelMemTableWriters() - 将 group 内的所有 writers 设置为 `STATE_PARALLEL_MEMTABLE_WRITER` - 调用 `SetState` - 优化:并行唤醒 [Optimizations in notify-one #12545](https://github.com/facebook/rocksdb/pull/12545) ### CompleteParallelMemTableWriter() - leader 和 follower 写完 memtabel 后都要调用,检查自己是否为最后一个完成的 - 如果不是:则调用 `AwaitState` 等待自身变成 `STATE_COMPLETED`后(所有 writers 都完成写入 memtable ),返回 false - leader 的 `STATE_COMPLETED` 在 `ExitAsBatchGroupFollower` 中设置 - folloer 的 `STATE_COMPLETED` 在 `ExitAsBatchGroupLeader` 中设置 - 如果是:直接返回 true,由上层调用者负责 group 写入的收尾工作。 - 最后一个完成的负责收尾其实就是实现了等待所有的 memtable 写入都完成才收尾。 - 并且所有写入的返回也需要等待 ---- # DBImpl Write 相关 - `DBImpl::seq_per_batch_` 默认是 false, 即每个 key 消耗一个 seq。 ## PreprocessWrite() - 检查 WAL 大小是否超出 `max_total_wal_size` 限制,如果超出则强制 flush 和 切换 WAL - 检查 `write_buffer_manager_`是否需要 flush(即内存接近 buffer manager 的限制) - 检查 `trim_history_scheduler_`,不为空则 `TrimMemtableHistory()` - 删除已 flush 过的 immutable memtable - 检查 `flush_scheduler_`,不为空则调度 Flush (`ScheduleFlushes()`) - DelayWrite 检测 - 检测 `write_buffer_manager_`,是否达到阈值需要停写(`ShouldStall()`) ## ConcurrentWriteToWAL() - 将 group 内的所有 batches 写入 WAL - 入参: - **`write_group`**:`WriteGroup&`,写入的目标 group - **`seq_inc`**:`size_t`,需要分配多少序列号,默认是 group 内 key 的个数 - **`last_sequnce`**:`SequenceNumber *`,分配的 seqnum,函数内赋值 1. **MergeBatch**:将 group 内所有 writers 的 WriteBatch 合并到一起 - 合并结果:`merge_batch` - 去除每个 batch 的 header,只合并数据部分,合并后使用同一个 seq 2. 持有 `log_write_mutex_` 锁 3. 分配序列号(从`VersionSet`中 增长`seq_inc`个, 使用 last_seq + 1),赋值给 `merge_batch` 和 `last_sequnce` 4. 调用 `WriteWAL(merged_batch, ..)` 将`merge_batch`追加写入到 WAL - 目标文件:`logs_.back()` - group write:整个 write group 对应一条 WAL record - [x] 为啥叫 Concurrent - 相比 `WriteToWAL(write_group, ...)` 内部加了 log 锁,支持并发调用 ## WriteImpl() - 只整理了一般写入流程(非 unordred write、非 pipelined write、非 two queue、非 disableWAL 等) ```mermaid --- title: 一般写入流程 --- stateDiagram-v2 [*] --> NewWriter NewWriter: 基于 WriteBatch 构建 Writer NewWriter --> JoinBatchGroup JoinBatchGroup: 调用 JoinBatchGroup 加入队列 JoinBatchGroup --> GetState GetState: 检查Writer的State state CheckJoinState <<choice>> GetState --> CheckJoinState CheckJoinState --> [*]: STATE_COMPLETED(没有并行写Memtable, Leader代替写完了 WAL+MemTable) CheckJoinState --> FollowerWriteMemtable: STATE_PARALLEL_MEMTABLE_WRITER(Leader通知并行写Memtable) FollowerWriteMemtable: Follwer写自己的batch到Memtable FollowerWriteMemtable --> CompleteParallelMemTableWriter CompleteParallelMemTableWriter: 调用 CompleteParallelMemTableWriter,返回Follower是否是最后一个完成写入的 state CheckLastWriter <<choice>> CompleteParallelMemTableWriter --> CheckLastWriter CheckLastWriter --> SetLastSequence: 是最后一个完成写入的,负责收尾结束group CheckLastWriter --> WaitCompleted: 不是 WaitCompleted: 等待STATE_COMPLETED状态,即由其他人收尾 WaitCompleted --> [*] SetLastSequence: 调用 SetLastSequence 发布序列号 SetLastSequence --> ExitAsBatchGroupFollower ExitAsBatchGroupFollower: 调用 ExitAsBatchGroupFollower ExitAsBatchGroupFollower --> [*] CheckJoinState --> LeaderFlow: STATE_GROUP_LEADER LeaderFlow: Leader 流程 state LeaderFlow { [*] --> PreprocessWrite PreprocessWrite: PreprocessWrite(写入预处理) PreprocessWrite --> EnterAsBatchGroupLeader EnterAsBatchGroupLeader: EnterAsBatchGroupLeader(组建group) EnterAsBatchGroupLeader --> WriteToWAL WriteToWAL: WriteToWAL(写 WAL) WriteToWAL --> CheckParallel CheckParallel: 是否可以并行写 Memtable state IfParallel <<choice>> CheckParallel --> IfParallel IfParallel --> WriteAll: 不能并行写 WriteAll: Leader 写入整个group到 Memtable IfParallel --> LaunchParallelMemTableWriters: 可以并行写 LaunchParallelMemTableWriters: LaunchParallelMemTableWriters(通知followers开始并行写Memtable) LaunchParallelMemTableWriters --> LeaderWriteMemtable LeaderWriteMemtable: Leader写自己的batch到Memtable LeaderWriteMemtable --> CompleteParallelMemTableWriter2 CompleteParallelMemTableWriter2: CompleteParallelMemTableWriter WriteAll --> LeaderSetLastSequence LeaderSetLastSequence: 调用 SetLastSequence 发布序列号 LeaderSetLastSequence --> ExitAsBatchGroupLeader ExitAsBatchGroupLeader --> [*] state CheckLastWriter2 <<choice>> CompleteParallelMemTableWriter2 --> CheckLastWriter2 CheckLastWriter2 --> LeaderSetLastSequence: 是最后一个完成写入的,Leader 负责结束group CheckLastWriter2 --> WaitCompleted2: 不是 WaitCompleted2: 等待STATE_COMPLETED状态 即某个Follower收尾结束group WaitCompleted2 --> [*] } ``` - 可并行写 memtable 的条件(需要同时满足): - `DBOptions::allow_concurrent_memtable_write` 为 true - `write_group` 内有多个 writers(一个 writer 自然没有必要并行写) - writers 的 batches 内不能有 merge 操作 - 并行写 Memtable 条件下,由最后一个完成写 Memtable 的 writers 负责收尾结束当前 group - 无论 writer 是 leader 还是 follower - 结束当前 group 时会启动 下一轮 group 写入 - `ExitAsBatchGroupFollower` 比 `ExitAsBatchGroupLeader` 多了一步设置和通知 leader 为 `STATE_COMPLETED` (Leader 阻塞在`CompleteParallelMemTableWriter`调用中) ## Unordred Write - PR:[https://github.com/facebook/rocksdb/pull/5218](https://github.com/facebook/rocksdb/pull/5218) - 写入流程(`DBImpl::WriteImpl()` unordred 条件分支) 1. `WriteImplWALOnly()` 2. `UnorderedWriteMemtable()` ### WriteImplWALOnly() - **1**. 调用 `JoinBatchGroup` 将 writer 入队 - **2/F**. Followers: 等待 `STATE_COMPLETED`,函数返回 - **2/L**. Leader: 1. `PreprocessWrite`:写前预处理 2. `EnterAsBatchGroupLeader`:组建 group 3. `ConcurrentWriteToWAL`:写 WAL 4. `versions_->SetLastSequence()`:直接发布序列号 5. `ExitAsBatchGroupLeader`:结束 group,唤醒等待在 `2/F` 的 followers ### UnorderedWriteMemtable() - 当前 writer 写自己的 memtable(可能是 leader 也可能是 follower)。 ### 总结 - 仍然由 Leader 写 WAL,但写完就发布序列号(不用等 memtable 写完) - 对于每个 writer 来说,仍然是写完 memtable 才算写完,可以读到自己写入的。 - 但提前发布序列号,memtable会写入过去的序列号,导致破坏了快照不变性的语义。 - 优势: - Leader 写完 WAL 就通知下一轮写入,且每个写操作不用等待组内写 memtable 最慢的 writer,延迟/吞吐都能提升。 ---- # Write 总结 - leader 单线程批量写 WAL后,followers 和 leader 并行写 memtable - 非 `unordred_write`,需要等待最慢的 memtable (即所有的)写入完成,才能开启下轮 leader 写入 - 一次 write group 有大小限制(见 EnterAsBatchGroupLeader )  - 序列号的分配: - ![](https://img.jonahgao.com/oss/note/2025p2/rocksdb_write_seqs.svg) ------- # Write Delay - 触发时机:在 **PreprocessWrite()** 时,检测 WriteController 是否需要 Stop 或者 Delay,需要的话调用 DelayWrite 函数(使用上一次 batch 的大小) - [ ] 为何无法获取到当前 batch 的大小 ## WriteController ^cc4333 - 用于实现 pre-DB 的 write delay / stop。 - Commit: [Push- instead of pull-model for managing Write stalls](https://github.com/facebook/rocksdb/commit/a2bb7c3c332f226a435d81845fe53a2a7c63d38f) ### 核心成员 - **`total_stopped_`**:`atomic<int>`,大于 0 表示需要 stop - **`total_delayed_`**:`atomic<int>`,大于 0 表示需要 delay - **`total_compaction_pressure_`**:`atomic<int>`, - **`low_pri_rate_limiter_`**:`unique_ptr<RateLimiter>` - **`max_delayed_write_rate_`**:写入 delay 速率的上界值,来自于 DBOptions - **`delayed_write_rate_`**:当前的写入 delay 速率,单位 `bytes / second`。 ### Token 类 - **WriteControllerToken** - 所有 token 的基类。 - **StopWriteToken** - 析构时,`total_stopped_`减一。 - **DelayWriteToken** - 析构时,`total_delayed_` 减 - **CompactionPressureToken** - 析构时,`total_compaction_pressure_` 减一 ### GetStopToken() - 某个 cf 请求停止写入,`total_stopped_` 加一 - 返回一个 token,当 token 被释放后 `total_stopped_` 减一。 - 即当所有 stop tokens 都被释放后,才能恢复停写。 ### GetDelayToken() - 入参: - **`write_rate`**:`uint64_t`,延迟速率 - 某个 cf 请求延迟写入,`total_delayed_` 加一,并设置延迟速率。 - 返回一个 token,当 token 被释放后 `total_delayed_` 减一。 - 即当所有 delay tokens 都被释放后,才能恢复延迟写入。 - 首次延迟(即 `total_delayed_` 之前为 0),会重置 delay 相关的 counters。 - 如 `next_refill_time_`,`credit_in_bytes` ### GetCompactionPressureToken() - `total_compaction_pressure_` 加一,返回一个 `CompactionPressureToken`。 ### NeedSpeedupCompaction() - 满足以下任一条件: - IsStopped() - NeedsDelay() - `total_compaction_pressure_ > 0` ### GetDelay() - 入参: - **`num_bytes`**:`uint64_t` - 写入 `num_bytes` 需要延迟多少微秒。 ## ColumnFamilyData ### WriteStallCondition 枚举 ^1fc93e - 三个枚举值 - kDelayed - kStopped - kNormal ### WriteStallCause 枚举 - CF 级别的 write stall 原因 - kMemtableLimit - kL0FileCountLimit - kPendingCompactionBytes - DB 级别的 write stall 原因 - kWriteBufferManagerLimit ### GetWriteStallConditionAndCause() - 返回值:`pair<WriteStallCondition, WriteStallCause>` - 触发 write stall 的三个指标 - **`unflushed_memtables`**:未完成 flush 的 memtable 数量 - **`num_l0_files`**:L0文件数量 - **`compaction_needed_bytes`**:需要/pending 的 compaction 数据量 | 指标阈值 | WriteStallCondition | WriteStallCause | | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------- | ----------------------- | | unflushed_memtables > max_write_buffer_number | kStop | kMemtableLimit | | 开启了 auto_compactions 且 num_l0_files 大于 level0_stop_writes_trigger` | kStop | kL0FileCountLimit | | 开启了 auto_compactions 且 compaction_needed_bytes 大于 hard limit | kStop | kPendingCompactionBytes | | max_write_buffer_number > 3 且 unflushed_memtables > max_write_buffer_number -1 且 unflushed_memtables -1 > min_write_buffer_number_to_merge | kDelayed | kMemtableLimit | | 开启了 auto_compactions 且 num_l0_files 大于 level0_slowdown_writes_trigger | kDelayed | kL0FileCountLimit | | 开启了 auto_compactions 且compaction_needed_bytes 大于 soft limit | kDelayed | kPendingCompactionBytes | - 不触发 write stall 则返回 { kNormal, kNone } ### StepupDelay - 当 cf 需要 delay 时,向 WriteController 申请 delay token 设置限速速率 - 入参: - **`write_controller`**:`WriteController *` - **`compaction_needed_bytes`** :`uint64_t` - cf 当前 superverson 的 pending compaction bytes - **`prev_compaction_need_bytes`**:`uint64_t` - cf 上一个 superverson 的 pending compaction bytes - **`penalize_stop`**:`bool`,当 WriteController 已经或接近 stop,需要更激进地限速 - **`auto_compactions_disabled`**:`bool` - 计算 `delayed_write_rate`: - 初始值为 WriteController 的上一次设置值 - 如果 `auto_compactions_disabled` 为 true,直接拉到最大 - 即 `DBOption::max_delayed_write_rate` - 如果 WriteController 之前已经 delay - 如果 `penalize_stop`,降低速率为 0.6 倍 - 如果 `compaction_needed_bytes` 比之前更大,降低为 0.8 倍(compaction 压力在变大),否则增大为 1/0.8 (compaction 压力在缓解) - 再根据计算好的新 `delayed_write_rate`,向 WriteController 设置 delay token。 ### RecalculateWriteStallConditions() - 重新计算 write stall 条件 - 在 `ColumnFamilyData::InstallSuperVersion()`中调用 - 当 compaction 完成、新 memtable 添加、flush 完成时 - 调用 `GetWriteStallConditionAndCause()` 判断当前 cf 是否满足 delay 或 stop 条件 - cf 满足 stop 条件后的处理流程: - 向 WriteController 申请 stop token,并更新统计信息、打印日志后返回。 - cf 满足 delay 条件后的处理流程: - `near_stop`: - cause 为 kPendingCompactionBytes:`compaction_needed_bytes` 比 soft limit 多出 `(hard_limit-soft_limit) * 3/4` - cause 为 kL0FileCountLimit:L0文件数量接近 `level0_stop_writes_trigger` - `penalize_stop`: `was_stop || near_stop` - `was_stop` 当前已经发生 stop 了 - 调用 StepupDelay 设置 delay,返回 token 保存到 `write_controller_token_` - cf 不需要 delay 和 stop 的处理流程: - 即 `write_stall_condition == WriteStallCondition::kNormal` - 当满足以下条件时,需要加速 compaction 即通过 `GetCompactionPressureToken()` 获取 token 保存到 `write_controller_token_`: - L0 数量较多,大于 `GetL0FileCountForCompactionSpeedup()` - 没有设置 `soft_pending_compaction_bytes_limit` - pending 的 compaction bytes 较多,大于 `GetPendingCompactionBytesForCompactionSpeedup()` - `FilesMarkedForCompaction()`大于 2 - 如果都不满足,reset `write_controller_token_`,释放 token - 如果 controller 之前需要 delay,但当前 cf 没有 delay,则增大 `delayed_write_rate` - DB 可能从 delay 中恢复过来了 - [ ] 这是否合理,因为可能其他 cf 的 compaction 压力还很大 ## DBImpl ### DelayWrite() - 只有 group leader 会调用。(PreprocessWrite 中调用) - delay 处理 - 调用 `write_controller_.GetDelay()`,使用的 `num_bytes`是上次 group 写入数据量,取得需要 delay 的微秒数 - 如果需要 delay 即大于 0 - `write_options`设置了 `no_slowdown`,直接返回 `Status::Incomplete` 1. WriteThread 进入 stall(调用 `BeginWriteStall) - 将 group 内其他设置了 `no_slowdown`的 writers 设置为失败 - 入队 `write_stall_dummy_` ,阻止新的 writer 入队 2. 进入循环 sleep,每次 sleep 0.001 秒,直到 `write_controller_.NeedsDelay()` 为 false,或者 sleep 够 delay 微秒数 3. WriteThread 结束 stall(调用 `EndWriteStall`) - 将 `write_stall_dummy_` 出队,运行新 writer 入队 - 通知 `stall_cv_`,唤醒被阻塞入队的 writers - stop 处理 - 调用 `write_controller_.IsStopped()`,判断是否发生 write stop - 如果需要 stop: - `write_options`设置了 `no_slowdown`,直接返回 `Status::Incomplete` 1. WriteThread 进入 stall(调用 `BeginWriteStall) 2. 等待 `bg_cv_`(等待 compaction 或者 flush ) 3. WriteThread 结束 stall(调用 `EndWriteStall`) - 重新检测是否需要 stop,重复步骤 1-3 ## 总结 - cf 的 write stall 检查在 InstallSuperVersion 时触发。 - 当某个 cf 需要 write stall 时,向 `DBImpl::write_controller_` 申请一个 token,保存到 cf 的 `write_controller_token_` - cf write stall 条件取消时,cf 释放自己的 `write_controller_token_` - 所有 cf 都不持有 token 时,才会取消 DB 的 write stall。 - 发生 write stop 后,写入需要等待 `DBImpl::bg_cv_` ,等待 compaction 完成后,再次检查。 - `DBImpl::DelayWrite()` 中进行。 ------- # 其他 - [x] write stall 的触发条件,影响粒度(一个write batch 还是 write group) - leader 写WAL前执行,影响整个 group - [ ] Pipeline write PR:[https://github.com/facebook/rocksdb/pull/2286](https://github.com/facebook/rocksdb/pull/2286) - [ ] WriteThread::AwaitState 的 yield 阶段的详细优化 - [x] 写入序列号何时分配 - ConcurrentWriteToWAL() 中 - [x] 何时调用 WAL sync - [x] need_log_sync - [x] 何时判断 flush - [ ] [Do not hold mutex when write keys if not necessary #7516](https://github.com/facebook/rocksdb/pull/7516) - [ ] benchmark:普通 write vs pipeline write vs unordered write