Compare commits
4 Commits
0047d52541
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e9836c931 | ||
|
|
b037c1a382 | ||
|
|
a98c144c51 | ||
|
|
f6a2cd979e |
@@ -104,22 +104,42 @@
|
|||||||
① 写请求进入 commit queue
|
① 写请求进入 commit queue
|
||||||
② WAL writer 收集当前队列中的多条写入,组成 WAL Batch
|
② WAL writer 收集当前队列中的多条写入,组成 WAL Batch
|
||||||
③ 等待组提交触发(500µs 或 32KB,先到者触发)
|
③ 等待组提交触发(500µs 或 32KB,先到者触发)
|
||||||
④ 为 Batch 内每条写入分配递增的 sequence number
|
④ 完成 Batch 校验与 WAL 编码前置准备
|
||||||
⑤ 编码 WAL Batch,并拆分为一个或多个 Physical Record 写入 WAL 文件
|
⑤ 预留 MemTable Arena 容量;不足时先冻结当前 MemTable 并切换到新的可写 MemTable
|
||||||
⑥ 写入 MemTable,但标记为 pending / unpublished
|
⑥ 为 Batch 内每条写入分配递增的 sequence number,并在内存中编码 WAL Batch
|
||||||
⑦ WAL Batch fsync 落盘
|
⑦ 将编码后的 WAL Batch 拆分为一个或多个 Physical Record,追加写入 WAL 文件
|
||||||
⑧ 发布 publishedSequence,使 Batch 内写入对普通读可见
|
⑧ 写入 MemTable,但标记为 pending / unpublished
|
||||||
⑨ 唤醒 Batch 内每个调用方,分别返回客户端"写入成功"
|
⑨ WAL Batch fsync 落盘
|
||||||
|
⑩ 发布 publishedSequence,使 Batch 内写入对普通读可见
|
||||||
|
⑪ 唤醒 Batch 内每个调用方,分别返回客户端"写入成功"
|
||||||
```
|
```
|
||||||
|
|
||||||
默认模式下,写入成功发生在 WAL fsync 之后;但 fsync 的粒度是 WAL Batch,不是单条 `Put`。因此多条写入共享一次 fsync 成本,同时每条写入仍然只有在自身所在 WAL Batch 持久化后才算成功。
|
默认模式下,写入成功发生在 WAL fsync 之后;但 fsync 的粒度是 WAL Batch,不是单条 `Put`。因此多条写入共享一次 fsync 成本,同时每条写入仍然只有在自身所在 WAL Batch 持久化后才算成功。
|
||||||
|
|
||||||
如果 WAL encode / write 在步骤 ⑤ 失败,且 Batch 尚未进入步骤 ⑥ 写入 MemTable,该 WAL Batch 不发布,也不会产生 pending / aborted MemTable entry;Batch 内调用方返回错误。由于 sequence 已经分配且不复用,引擎必须进入 write-stopped 状态,不再接受新写入,直到关闭并完成恢复;否则 `publishedSequence` 无法跳过失败 Batch 推进,后续成功 Batch 也会因为 sequence 空隙而不可见。这样把纯 WAL failure 与已进入 MemTable 的未知提交状态分开处理,同时保持 `publishedSequence` 始终是连续 high-water mark。
|
步骤 ⑤ 是 WAL 副作用之前的强制容量关口:WAL writer 必须按 Batch 内所有 entry 的最大内存占用(key、value 或 ValueLogPointer、skiplist 节点、arena 对齐与层高开销)计算需要预留的 Arena 字节数。若当前可写 MemTable 剩余容量不足,必须先把它冻结为 Immutable MemTable,并创建新的可写 MemTable 后再继续。若 Immutable MemTable 队列已达上限,该 Batch 必须在 WAL write 之前等待后台 flush 释放容量;若单个 Batch 的最大可能内存占用超过 MemTable 上限,则该 Batch 在进入 WAL 前按普通错误拒绝。实现不得在 WAL write / append 已尝试之后,再因为 Arena 满而让 MemTable pending 写入失败。
|
||||||
|
|
||||||
如果 Batch 已经完成步骤 ⑥ 写入 MemTable,但 WAL bytes 已写入后 fsync 返回错误,该 WAL Batch 在当前运行期仍不发布、不确认成功,Batch 内调用方返回 `ErrCommitUnknown`,而不是普通失败错误;因为它的持久化状态是 unknown,而不是 definitely lost。sequence 一旦分配不复用;引擎进入 write-stopped 状态,不再接受新写入,直到关闭并完成恢复。因此 `publishedSequence` 始终保持连续 high-water mark,普通读仍可使用 `sequence <= publishedSequence` 判断可见性,失败 Batch 的 entries 在当前运行期永远不会对普通读可见。重启后,如果 recovery 在 WAL 中发现该 Batch 完整、CRC 合法且 sequence 连续,可以按正常 WAL 规则重放。
|
步骤 ⑧ 与 ⑩ 之间存在明确的内存序约束:MemTable skiplist / arena 节点必须先通过原子发布机制写入读路径可见结构(例如 `atomic.Pointer` store-release,或等价的 release publish),并且 Batch 内所有 entry 的节点都完成发布后,才能用 `atomic.Uint64.Store` 推进 `publishedSequence`。普通读者必须先 `Load` 当前 `publishedSequence`,再遍历 MemTable;读到 entry 后仍以 `entry.sequence <= loadedPublishedSequence` 判断可见性。该顺序保证弱内存序架构上读者不会先观察到已推进的 `publishedSequence`,却看不到对应已发布的 skiplist 节点。
|
||||||
|
|
||||||
|
步骤 ⑥ 必须只做内存内编码,不得触碰 WAL 文件或任何可能被 recovery 看到的 WAL 缓冲区。若 Batch 校验、MemTable 容量预留或内存编码在步骤 ⑥ 之前失败,且尚未分配 sequence,该 Batch 可以按普通错误返回,调用方可安全地认为本次提交没有进入 WAL。若实现已经分配了 sequence 且不复用,即使失败尚未触碰 WAL,也必须进入 write-stopped 状态;否则 `publishedSequence` 无法跳过失败 Batch 推进,后续成功 Batch 也会因为 sequence 空隙而不可见。因此实现应尽量把可失败的校验、容量预留与编码前置到 sequence 分配之前,减少无 WAL 副作用的普通错误触发 write-stopped。
|
||||||
|
|
||||||
|
步骤 ⑦ 一旦尝试 WAL write / append,后续错误的判断标准不再是 Batch 是否已经进入 MemTable,而是 WAL bytes 是否可能已经到达 recovery 可见状态。`write()` 返回错误时,部分或全部 bytes 可能已经进入 OS page cache、WAL 文件或实现内部的 WAL buffer;即使 Batch 尚未进入 MemTable,也不能把它当作 definitely failed。除非实现能证明 zero bytes reached WAL state(例如未调用 write,或明确的内部缓冲协议证明没有任何 byte 被提交给 WAL 状态),否则 Batch 内调用方必须返回 `ErrCommitUnknown`,引擎进入 write-stopped 状态。
|
||||||
|
|
||||||
|
WAL 写入路径的失败分类如下:
|
||||||
|
|
||||||
|
| 失败点 | WAL 副作用 | 客户端结果 | 引擎状态 |
|
||||||
|
|--------|------------|------------|----------|
|
||||||
|
| Batch 校验 / MemTable 容量预留 / 内存编码失败,且尚未分配 sequence | 无 | 普通错误 | 可继续写入 |
|
||||||
|
| Batch 校验 / MemTable 容量预留 / 内存编码失败,但 sequence 已分配且不复用 | 无 | 普通错误 | write-stopped |
|
||||||
|
| WAL write / append 已尝试后失败,且无法证明 zero bytes reached WAL state | 可能有 | `ErrCommitUnknown` | write-stopped |
|
||||||
|
| WAL write / append 已成功,但 MemTable pending 写入违反预留保证仍然失败 | 可能有 | `ErrCommitUnknown` | write-stopped |
|
||||||
|
| WAL bytes 已写入,MemTable pending 写入后 fsync 失败 | 可能有 | `ErrCommitUnknown` | write-stopped |
|
||||||
|
|
||||||
|
`ErrCommitUnknown` 的 Batch 在当前运行期仍不发布、不确认成功;如果已经写入 MemTable,其 entries 保持 pending / unpublished 或转为 aborted,仅作为内部状态存在,对普通读不可见。`publishedSequence` 始终保持连续 high-water mark,普通读仍可使用 `sequence <= publishedSequence` 判断可见性。重启后,如果 recovery 在 WAL 中发现该 Batch 完整、CRC 合法且 sequence 连续,可以按正常 WAL 规则重放;如果只留下尾部 partial write,则按 WAL 尾部截断规则处理。
|
||||||
|
|
||||||
`ErrCommitUnknown` 表示提交结果不确定:调用方不能把它当作“写入一定失败”并盲目重试。恢复完成后,调用方必须通过读取 key 或后续事务层提供的事务 ID / commit record 查询提交状态,再决定是否重试。后续 MVCC / SSI 事务层必须为事务提交提供幂等标识,避免 fsync 不确定结果导致非幂等事务重复提交。
|
`ErrCommitUnknown` 表示提交结果不确定:调用方不能把它当作“写入一定失败”并盲目重试。恢复完成后,调用方必须通过读取 key 或后续事务层提供的事务 ID / commit record 查询提交状态,再决定是否重试。后续 MVCC / SSI 事务层必须为事务提交提供幂等标识,避免 fsync 不确定结果导致非幂等事务重复提交。
|
||||||
|
|
||||||
|
如果进程在 WAL write / append 已尝试之后、调用方收到成功或错误之前崩溃,调用方观察到的是“无返回结果”。该状态在 API 语义上等价于 `ErrCommitUnknown`:不是成功确认,也不是 definitely failed,而是 maybe committed。调用方重启后必须按同一套状态确认约束处理,不得因为没有收到成功返回就假设该写入一定不存在。
|
||||||
|
|
||||||
#### ErrCommitUnknown 的 API 层确认约束
|
#### ErrCommitUnknown 的 API 层确认约束
|
||||||
|
|
||||||
`ErrCommitUnknown` 在 API 层的含义是 **maybe committed**:该写入可能已经持久化并会在恢复后可见,也可能没有持久化。它不是 rollback 语义,也不是 definitely failed。调用方不得仅凭错误类型盲目重试,除非该逻辑操作本身幂等,且业务允许覆盖后续并发写入。
|
`ErrCommitUnknown` 在 API 层的含义是 **maybe committed**:该写入可能已经持久化并会在恢复后可见,也可能没有持久化。它不是 rollback 语义,也不是 definitely failed。调用方不得仅凭错误类型盲目重试,除非该逻辑操作本身幂等,且业务允许覆盖后续并发写入。
|
||||||
@@ -198,6 +218,8 @@ batch 所在 segment 已进入 durable-ready 状态
|
|||||||
|
|
||||||
如果新 segment 进入 durable-ready 之前任一步失败,该 segment 不得成为 active segment,也不得承载可确认写入。如果此时尚未分配 sequence,可以重试创建或切换到其他 segment;如果 sequence 已分配或已有 WAL Batch 依赖该 segment,则按 WAL write failure 处理,引擎进入 write-stopped 状态。
|
如果新 segment 进入 durable-ready 之前任一步失败,该 segment 不得成为 active segment,也不得承载可确认写入。如果此时尚未分配 sequence,可以重试创建或切换到其他 segment;如果 sequence 已分配或已有 WAL Batch 依赖该 segment,则按 WAL write failure 处理,引擎进入 write-stopped 状态。
|
||||||
|
|
||||||
|
新 segment 的 durable-ready 协议只能在当前 active segment 已结束于完整 WAL Batch 边界后启动。实现不得预创建未来 segment,也不得让 `segment-N+1.wal` 在 `segment-N.wal` 仍可能存在未完成 Batch 尾部时对 recovery 可见。该约束保证:如果 recovery 看到后续 segment 存在,前一个 segment 必须已经 sealed 在完整 Batch 边界;否则前一个 segment 的尾部异常应被视为 WAL 中间损坏。
|
||||||
|
|
||||||
MANIFEST 不在每次 WAL segment 轮转时更新。MANIFEST 表示 recovery 起点 / checkpoint 状态,只在 MemTable flush、SSTable 与 checkpoint 元数据都持久化后推进;旧 WAL segment 何时可删除由后续文件生命周期规则定义。
|
MANIFEST 不在每次 WAL segment 轮转时更新。MANIFEST 表示 recovery 起点 / checkpoint 状态,只在 MemTable flush、SSTable 与 checkpoint 元数据都持久化后推进;旧 WAL segment 何时可删除由后续文件生命周期规则定义。
|
||||||
|
|
||||||
#### 设计决策
|
#### 设计决策
|
||||||
@@ -309,6 +331,14 @@ Physical Record 的顺序由 WAL 文件的顺序追加和顺序扫描保证,
|
|||||||
- Physical Record 的 `length` 必须 `> 0`
|
- Physical Record 的 `length` 必须 `> 0`
|
||||||
- 读取时,Block 尾部 padding 必须全为 0;如果 padding 区出现非 0 bytes,视为 WAL 损坏
|
- 读取时,Block 尾部 padding 必须全为 0;如果 padding 区出现非 0 bytes,视为 WAL 损坏
|
||||||
|
|
||||||
|
##### Segment Rotation 约束
|
||||||
|
|
||||||
|
- WAL Batch 不得跨 segment 文件;每个 WAL Batch 的所有 Physical Record 必须位于同一个 WAL segment 内
|
||||||
|
- Segment rotation 只在 WAL Batch 边界发生:当前 segment 写入完一个完整 WAL Batch 后,如果需要轮转,在下个 Batch 写入前切换到新 segment
|
||||||
|
- 写入 WAL Batch 前,WAL writer 必须根据编码后的 Batch 大小计算 Physical Records 布局;如果当前 segment 剩余空间不足以容纳整个 Batch,必须先切换到新 segment,再写入该 Batch
|
||||||
|
- 不允许先写入 Batch 的部分 fragment,再因 segment 空间不足切换 segment
|
||||||
|
- Recovery 的 fragment 收集状态不跨 segment 携带;每个非最后恢复 segment 扫描结束时必须处于 `Idle`
|
||||||
|
|
||||||
##### WAL Batch
|
##### WAL Batch
|
||||||
|
|
||||||
WAL Batch 是物理持久化单元,对应一次 group commit batch。恢复时必须拼出完整 WAL Batch 后才能重放,不能重放半个 batch。WAL Batch 不是事务边界;一个 WAL Batch 可以包含多个独立 autocommit 写入,后续也可以包含一个或多个事务提交记录。
|
WAL Batch 是物理持久化单元,对应一次 group commit batch。恢复时必须拼出完整 WAL Batch 后才能重放,不能重放半个 batch。WAL Batch 不是事务边界;一个 WAL Batch 可以包含多个独立 autocommit 写入,后续也可以包含一个或多个事务提交记录。
|
||||||
@@ -444,7 +474,11 @@ MANIFEST 是 recovery 起点和 checkpoint 状态的权威源,记录最老仍
|
|||||||
6. 在每个 Block 内按 Physical Record 顺序解析
|
6. 在每个 Block 内按 Physical Record 顺序解析
|
||||||
7. 将 Full 或 First/Middle/Last 拼成完整 WAL Batch
|
7. 将 Full 或 First/Middle/Last 拼成完整 WAL Batch
|
||||||
8. 校验并重放 WAL Batch 中的 Entries
|
8. 校验并重放 WAL Batch 中的 Entries
|
||||||
9. 更新 expectedSequence,并继续扫描下一个 batch 或下一个 segment
|
9. 更新 expectedSequence,并继续扫描下一个 batch
|
||||||
|
10. 当前 segment 所有 Block 扫描完毕后,检查 fragment 状态:
|
||||||
|
- Idle: 正常结束当前 segment,继续下一个 segment
|
||||||
|
- CollectingFragments 且当前 segment 是最后一个需要恢复的 segment: 丢弃 incomplete batch,并截断到最后一个完整 WAL Batch 结束位置
|
||||||
|
- CollectingFragments 且当前 segment 不是最后一个需要恢复的 segment: 视为 WAL 中间损坏,报错
|
||||||
```
|
```
|
||||||
|
|
||||||
###### Segment 连续性校验
|
###### Segment 连续性校验
|
||||||
@@ -522,8 +556,11 @@ CollectingFragments
|
|||||||
| CollectingFragments | Last | 追加 payload,拼出完整 WAL Batch,解析重放后回到 Idle |
|
| CollectingFragments | Last | 追加 payload,拼出完整 WAL Batch,解析重放后回到 Idle |
|
||||||
| CollectingFragments | Full | 非法 fragment 顺序 |
|
| CollectingFragments | Full | 非法 fragment 顺序 |
|
||||||
| CollectingFragments | First | 非法 fragment 顺序 |
|
| CollectingFragments | First | 非法 fragment 顺序 |
|
||||||
|
| Idle | segment 结束 | 正常结束当前 segment |
|
||||||
|
| CollectingFragments | segment 结束,且是最后恢复 segment | 丢弃 incomplete batch,并截断尾部 |
|
||||||
|
| CollectingFragments | segment 结束,且不是最后恢复 segment | WAL 中间损坏,报错 |
|
||||||
|
|
||||||
如果扫描到 WAL 尾部时仍处于 `CollectingFragments` 状态,说明最后一个 WAL Batch 未写完整,丢弃该 incomplete batch,并截断到最后一个完整 WAL Batch 结束位置。
|
Segment 边界不是合法的 fragment 边界。`First + Middle* + Last` 必须全部出现在同一个 segment 内。如果扫描到 WAL 尾部时仍处于 `CollectingFragments` 状态,说明最后一个 WAL Batch 未写完整,丢弃该 incomplete batch,并截断到最后一个完整 WAL Batch 结束位置。
|
||||||
|
|
||||||
###### WAL Batch 校验与重放
|
###### WAL Batch 校验与重放
|
||||||
|
|
||||||
@@ -584,6 +621,17 @@ publishedSequence = recoveredSequence
|
|||||||
|
|
||||||
因为 WAL 中恢复出来的数据都来自已经持久化的完整 batch,所以恢复后可以全部视为 published。
|
因为 WAL 中恢复出来的数据都来自已经持久化的完整 batch,所以恢复后可以全部视为 published。
|
||||||
|
|
||||||
|
这里的 published 表示“恢复后对普通读可见”,不表示崩溃前调用方一定已经收到成功确认。进程崩溃但机器未掉电时,已经通过 `write()` 进入 OS page cache、但尚未完成 fsync 或尚未唤醒调用方的完整 WAL Batch,可能在进程退出后被内核刷盘。重启后如果 recovery 发现该 Batch CRC 合法、sequence 连续且完整,就会按正常 WAL 规则重放并标记为 published。这不是数据丢失或 WAL 损坏,而是未确认写入的可见性前移。
|
||||||
|
|
||||||
|
因此 `Always` 策略的确认语义是不对称的:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Put 返回成功 => 写入所在 WAL Batch 已 fsync,进程崩溃或机器掉电后必须可恢复
|
||||||
|
Put 未返回成功 => 写入结果不确定;恢复后可能存在,也可能不存在
|
||||||
|
```
|
||||||
|
|
||||||
|
如果未来需要严格区分“调用方已确认成功”与“未确认但已经存在于 WAL”,必须引入额外的 durable commit marker、confirmed-sequence 元数据,或由后续事务层提供 client-visible `txnID` / 幂等 token 与 commit record。第一版 WAL recovery 不维护该区分;它只以完整性校验和 sequence 连续性决定是否重放。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 3.3 MemTable(内存表)
|
### 3.3 MemTable(内存表)
|
||||||
@@ -599,6 +647,17 @@ publishedSequence = recoveredSequence
|
|||||||
| Arena 大小 | 可配置,默认 64MB | 不同场景灵活调整;64MB 是 BadgerDB 验证过的安全值 |
|
| Arena 大小 | 可配置,默认 64MB | 不同场景灵活调整;64MB 是 BadgerDB 验证过的安全值 |
|
||||||
| 并发策略 | Mutex 写 + 无锁读 | 写入已被 WAL 组提交串行化,无锁写优势用不上;读是并发的,无锁读有价值 |
|
| 并发策略 | Mutex 写 + 无锁读 | 写入已被 WAL 组提交串行化,无锁写优势用不上;读是并发的,无锁读有价值 |
|
||||||
|
|
||||||
|
#### 发布与内存序
|
||||||
|
|
||||||
|
MemTable 的“Mutex 写 + 无锁读”只表示写入侧用互斥锁串行化结构修改;它不允许依赖 Mutex 本身向未持锁读者发布内存。所有可能被无锁读遍历到的 skiplist 指针必须用原子操作发布,且发布顺序必须满足:
|
||||||
|
|
||||||
|
1. 写入侧在 Mutex 保护下完成节点内容初始化,包括 key、value、sequence 与 pending / aborted 状态。
|
||||||
|
2. 写入侧用 release 语义的原子 store 发布 skiplist 链接指针,使无锁读者只能看到初始化完整的节点。
|
||||||
|
3. WAL Batch 达到当前落盘策略的发布条件后,WAL writer 在所有 Batch entry 的 skiplist 节点都已原子发布之后,才用 `atomic.Uint64.Store` 推进 `publishedSequence`。
|
||||||
|
4. 无锁读者先用 `atomic.Uint64.Load` 读取一次 `publishedSequence`,再遍历 skiplist;遍历中只返回 `sequence <= loadedPublishedSequence` 且非 aborted 的 entry。
|
||||||
|
|
||||||
|
因此 `publishedSequence` 必须是 typed atomic(例如 `atomic.Uint64`),不能是普通 `uint64` 字段;skiplist next 指针也必须是 `atomic.Pointer` 或具备等价 release / acquire 语义的实现。该协议把“节点可见”放在“sequence 可见”之前,避免 ARM 等弱内存序平台出现读者看见新 sequence 却看不见对应节点的状态。
|
||||||
|
|
||||||
#### MemTable 生命周期
|
#### MemTable 生命周期
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -611,6 +670,23 @@ MemTable (可写, 64MB)
|
|||||||
释放 Immutable MemTable
|
释放 Immutable MemTable
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Arena 容量预留
|
||||||
|
|
||||||
|
MemTable 必须提供写入前容量预留能力,供 WAL writer 在追加 WAL 之前调用。预留必须覆盖整个 WAL Batch,而不是逐条 entry 边写边试:一旦预留成功,后续把 Batch 内所有 entry 写入 pending / unpublished MemTable 时,不得再因为 Arena 满返回错误。预留应绑定到具体的可写 MemTable generation;如果后续在 sequence 分配和 WAL append 之前发生 Batch 校验或内存编码失败,预留必须可以回滚,或通过丢弃该 generation 的方式保证不会泄漏 Arena 容量。
|
||||||
|
|
||||||
|
预留计算至少包含:key bytes、value bytes 或 ValueLogPointer bytes、skiplist node 固定字段、随机层高带来的 next 指针数组、arena 对齐 padding,以及实现需要的 entry metadata。为避免随机层高导致预留不足,预留必须按该 MemTable 最大层高的最坏情况计算,或在预留阶段一次性确定并记住每个 entry 的层高。
|
||||||
|
|
||||||
|
写入侧必须先用 checked arithmetic 判断 Batch 最坏情况预留量是否超过空 MemTable 的可用容量上限;这里的上限是扣除 MemTable 固定元数据后的 usable capacity,而不是原始配置的 Arena 字节数。若单个 Batch 本身不可能放入一个空 MemTable,必须在 sequence 分配和 WAL append 之前拒绝该 Batch,不得先等待 flush 或追加 WAL。
|
||||||
|
|
||||||
|
当 Batch 可以放入空 MemTable、但当前可写 MemTable 剩余容量不足时,写入侧必须在 WAL write 之前执行 freeze + switch:
|
||||||
|
|
||||||
|
1. 如果 Immutable MemTable 队列未满,冻结当前 MemTable,创建新的可写 MemTable,并在新 MemTable 上重新执行预留。
|
||||||
|
2. 如果 Immutable MemTable 队列已满,阻塞新 Batch,等待后台 flush 释放队列名额;等待期间不得先追加 WAL。
|
||||||
|
|
||||||
|
如果实现违反上述预留协议,导致 WAL append 已经成功后 MemTable pending 写入仍因 Arena 满或 generation mismatch 失败,该 Batch 不能按普通错误处理;必须按 `ErrCommitUnknown` 返回并进入 write-stopped,因为 WAL bytes 已可能被 recovery 观察到。
|
||||||
|
|
||||||
|
该约束把 Arena 满从“WAL 已有副作用后的不确定提交”前移为“WAL 前的容量/背压决策”,避免出现 WAL 中存在完整 Batch、但当前运行期 MemTable 没有对应 pending entry 的状态。
|
||||||
|
|
||||||
#### Flush 过滤规则
|
#### Flush 过滤规则
|
||||||
|
|
||||||
MemTable 可能包含已写入内存但尚未发布的 pending entry,也可能包含 fsync 失败后保留的 aborted entry。Flush 到 SSTable 时必须过滤这些 entry:
|
MemTable 可能包含已写入内存但尚未发布的 pending entry,也可能包含 fsync 失败后保留的 aborted entry。Flush 到 SSTable 时必须过滤这些 entry:
|
||||||
|
|||||||
Reference in New Issue
Block a user