# 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 )
- 序列号的分配:
- 
-------
# 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