update WAL write failure design
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -104,19 +104,31 @@
|
||||
① 写请求进入 commit queue
|
||||
② WAL writer 收集当前队列中的多条写入,组成 WAL Batch
|
||||
③ 等待组提交触发(500µs 或 32KB,先到者触发)
|
||||
④ 为 Batch 内每条写入分配递增的 sequence number
|
||||
⑤ 编码 WAL Batch,并拆分为一个或多个 Physical Record 写入 WAL 文件
|
||||
⑥ 写入 MemTable,但标记为 pending / unpublished
|
||||
⑦ WAL Batch fsync 落盘
|
||||
⑧ 发布 publishedSequence,使 Batch 内写入对普通读可见
|
||||
⑨ 唤醒 Batch 内每个调用方,分别返回客户端"写入成功"
|
||||
④ 完成 Batch 校验与 WAL 编码前置准备
|
||||
⑤ 为 Batch 内每条写入分配递增的 sequence number,并在内存中编码 WAL Batch
|
||||
⑥ 将编码后的 WAL Batch 拆分为一个或多个 Physical Record,追加写入 WAL 文件
|
||||
⑦ 写入 MemTable,但标记为 pending / unpublished
|
||||
⑧ WAL Batch fsync 落盘
|
||||
⑨ 发布 publishedSequence,使 Batch 内写入对普通读可见
|
||||
⑩ 唤醒 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 文件或任何可能被 recovery 看到的 WAL 缓冲区。若 Batch 校验或内存编码在步骤 ⑤ 失败,且尚未分配 sequence,该 Batch 可以按普通错误返回,调用方可安全地认为本次提交没有进入 WAL。若实现已经分配了 sequence 且不复用,即使失败尚未触碰 WAL,也必须进入 write-stopped 状态;否则 `publishedSequence` 无法跳过失败 Batch 推进,后续成功 Batch 也会因为 sequence 空隙而不可见。因此实现应尽量把可失败的校验与编码前置到 sequence 分配之前,减少无 WAL 副作用的普通错误触发 write-stopped。
|
||||
|
||||
如果 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 规则重放。
|
||||
步骤 ⑥ 一旦尝试 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 校验 / 内存编码失败,且尚未分配 sequence | 无 | 普通错误 | 可继续写入 |
|
||||
| Batch 校验 / 内存编码失败,但 sequence 已分配且不复用 | 无 | 普通错误 | write-stopped |
|
||||
| WAL write / append 已尝试后失败,且无法证明 zero bytes reached WAL state | 可能有 | `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 不确定结果导致非幂等事务重复提交。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user