update design doc
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
161
docs/design.md
161
docs/design.md
@@ -114,19 +114,45 @@
|
||||
|
||||
默认模式下,写入成功发生在 WAL fsync 之后;但 fsync 的粒度是 WAL Batch,不是单条 `Put`。因此多条写入共享一次 fsync 成本,同时每条写入仍然只有在自身所在 WAL Batch 持久化后才算成功。
|
||||
|
||||
如果 WAL write 失败或 WAL bytes 未完整写入,该 WAL Batch 不发布;对应 pending entries 保留在 MemTable 中,但保持 unpublished / aborted 状态,不做原地删除,Batch 内调用方返回错误。由于 sequence 已经分配且不复用,引擎必须进入 write-stopped 状态,不再接受新写入,直到关闭并完成恢复;否则 `publishedSequence` 无法跳过失败 Batch 推进,后续成功 Batch 也会因为 sequence 空隙而不可见。这样避免在无锁读和 Arena / SkipList 分配模型下引入并发删除与内存回收复杂度,同时保持 `publishedSequence` 始终是连续 high-water mark。
|
||||
如果 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 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 规则重放。
|
||||
如果 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 规则重放。
|
||||
|
||||
`ErrCommitUnknown` 表示提交结果不确定:调用方不能把它当作“写入一定失败”并盲目重试。恢复完成后,调用方必须通过读取 key 或后续事务层提供的事务 ID / commit record 查询提交状态,再决定是否重试。后续 MVCC / SSI 事务层必须为事务提交提供幂等标识,避免 fsync 不确定结果导致非幂等事务重复提交。
|
||||
|
||||
#### ErrCommitUnknown 的 API 层确认约束
|
||||
|
||||
`ErrCommitUnknown` 在 API 层的含义是 **maybe committed**:该写入可能已经持久化并会在恢复后可见,也可能没有持久化。它不是 rollback 语义,也不是 definitely failed。调用方不得仅凭错误类型盲目重试,除非该逻辑操作本身幂等,且业务允许覆盖后续并发写入。
|
||||
|
||||
第一阶段单 key autocommit 没有事务级 `txnID` / commit record,只能提供弱状态确认:恢复完成后读取 key 只能判断当前最终状态,不能证明“这一次尝试”是否提交。不同操作的确认能力如下:
|
||||
|
||||
| 操作 | 读取确认能力 | 约束 |
|
||||
| ---- | ------------ | ---- |
|
||||
| `Put(key, distinguishableValue)` | 如果恢复后 `Get(key)` 返回该 value,只能说明当前状态符合预期 | 若存在并发写入或旧值相同,不能证明本次 Put 已提交 |
|
||||
| `Put(key, emptyValue)` | 必须依赖 API 区分 key exists with empty value 与 key not found | `Get` 不能只返回 `[]byte`;必须返回 `found` 或等价状态 |
|
||||
| `Delete(key)` | 如果恢复后 key not found,只能说明当前 key 不存在 | key 可能原本就不存在,或被其他操作删除;不能证明本次 Delete 已提交 |
|
||||
| 重复写同一 key | 读取到相同 value 不能区分是哪一次写入提交 | 需要业务层 operation marker,或未来事务层幂等标识 |
|
||||
|
||||
因此,第一阶段 API 必须把单 key autocommit 的状态确认标记为弱确认;非幂等业务逻辑不得依赖它实现 exactly-once。后续 MVCC / SSI 事务层必须提供强确认机制:提交请求携带 client-visible `txnID` 或幂等 token,并把 commit record 与 mutation 原子持久化。`Commit(txnID)` 重试或 `CommitStatus(txnID)` 查询必须返回原始提交结果;同一 token 携带不同 mutation payload 必须被判定为冲突,而不是重新执行或静默覆盖。
|
||||
|
||||
#### write-stopped 后的 MemTable 生命周期
|
||||
|
||||
进入 write-stopped 后,引擎不再接受新写入,但已经存在的 MemTable / Immutable MemTable 可以继续被后台流程处理;处理边界必须以 `publishedSequence` 为准:
|
||||
|
||||
- 后台 freeze / flush 可以继续执行,但 flush 只能输出 `sequence <= publishedSequence` 且非 aborted 的 entry
|
||||
- pending / unpublished / aborted entry 只作为当前运行期内部状态存在;关闭进程后随 MemTable 内存一起丢弃,不写入 SSTable、MANIFEST 或 checkpoint 元数据
|
||||
- 含 pending / unpublished / aborted entry 的 MemTable 不得作为 checkpoint 推进依据;checkpoint / MANIFEST 只能覆盖已经发布、已刷入 SSTable 且 sequence 连续的 durable high-water mark
|
||||
- 后台 flush 即使生成了只含已发布 entry 的 SSTable,也不能让 MANIFEST 越过失败 Batch 对应的 sequence 空隙;`recoverySegmentID` 的推进必须保证恢复路径仍能从 WAL 判断失败 Batch 的最终状态
|
||||
- recovery 不读取运行期遗留的 pending / aborted MemTable entry,只从完整、CRC 合法、sequence 连续的 WAL Batch 恢复,并在恢复完成后把恢复出的数据视为 published
|
||||
|
||||
#### 可见性语义
|
||||
|
||||
- `publishedSequence` 表示普通读的逻辑可见 high-water mark;普通读可以读取 MemTable,但只返回 `sequence <= publishedSequence` 的 entry
|
||||
- fsync 前,写入可以已经存在于 MemTable 中,但处于 pending / unpublished 状态,仅供内部提交流程使用,对普通读不可见
|
||||
- pending / unpublished / aborted entry 不仅对普通读不可见,也不得进入 SSTable;MemTable flush 必须遵守 3.3 的 Flush 过滤规则,只刷 `sequence <= publishedSequence` 且非 aborted 的 entry
|
||||
- `Always` 默认策略下,`publishedSequence` 也是 durable high-water mark;普通读能读到的数据,必须是崩溃恢复后仍可恢复的数据
|
||||
- `Periodic` / `Never` 策略下,`publishedSequence` 可以领先于 durable high-water mark;普通读能读到当前进程内已发布的数据,但这些数据不保证机器掉电后仍可恢复
|
||||
- 如果 WAL Entry 引用外部持久化对象(例如 Value Log record),发布 WAL Batch 前,被引用对象也必须满足当前落盘策略对应的持久化要求;在 `Always` 下这意味着被引用对象必须已持久化,避免恢复后出现悬空指针
|
||||
- 如果 WAL Entry 引用外部持久化对象(例如 Value Log record),发布 WAL Batch 前,被引用对象也必须满足当前落盘策略对应的持久化要求;在 `Always` 下这意味着该 Batch 引用的 Value Log ranges 必须先通过 3.9 的 durable barrier,避免恢复后出现悬空指针
|
||||
- 该模型为后续 MVCC / 事务层提供统一的 read timestamp / commit sequence 基础
|
||||
- `Always` 下该选择牺牲的是写入进入 MemTable 后到 fsync 发布前的短暂全局可见性延迟,不是已发布数据的读路径性能
|
||||
|
||||
@@ -140,6 +166,40 @@
|
||||
|
||||
非默认落盘策略必须由用户显式开启。内部仍使用同一套 sequence / publish 机制,区别只在于 `publishedSequence` 是在 fsync 后推进,还是在 WAL write 成功后提前推进。`Periodic` / `Never` 下,已经发布并返回成功的写入仍可能在机器掉电后丢失。
|
||||
|
||||
#### WAL 元数据持久化协议
|
||||
|
||||
`Always` 策略下,WAL Batch 可以返回成功的前提不仅是 WAL bytes 已 fsync,还包括恢复路径能够在掉电后找到这些 bytes。任何承载已确认写入的 WAL segment 都必须先进入 durable-ready 状态。
|
||||
|
||||
新 WAL segment 创建流程:
|
||||
|
||||
```text
|
||||
1. create segment-N.wal.tmp
|
||||
2. write WAL File Header
|
||||
3. fsync segment-N.wal.tmp
|
||||
4. rename segment-N.wal.tmp → segment-N.wal
|
||||
5. fsync WAL directory
|
||||
6. update CURRENT via temp + rename:
|
||||
a. write CURRENT.tmp = segment-N.wal
|
||||
b. fsync CURRENT.tmp
|
||||
c. rename CURRENT.tmp → CURRENT
|
||||
d. fsync metadata directory
|
||||
7. segment-N enters durable-ready state
|
||||
8. WAL writer may append batches whose recovery depends on segment-N
|
||||
```
|
||||
|
||||
`Always` 下,一个 WAL Batch 只有同时满足以下条件才能确认成功并发布 sequence:
|
||||
|
||||
```text
|
||||
batch 所在 segment 已进入 durable-ready 状态
|
||||
∧ WAL bytes 已写到 batch end offset
|
||||
∧ WAL 文件已 fsync 到 batch end offset
|
||||
∧ 发现该 segment 所需的元数据已经 fsync
|
||||
```
|
||||
|
||||
如果新 segment 进入 durable-ready 之前任一步失败,该 segment 不得成为 active segment,也不得承载可确认写入。如果此时尚未分配 sequence,可以重试创建或切换到其他 segment;如果 sequence 已分配或已有 WAL Batch 依赖该 segment,则按 WAL write failure 处理,引擎进入 write-stopped 状态。
|
||||
|
||||
MANIFEST 不在每次 WAL segment 轮转时更新。MANIFEST 表示 recovery 起点 / checkpoint 状态,只在 MemTable flush、SSTable 与 checkpoint 元数据都持久化后推进;旧 WAL segment 何时可删除由后续文件生命周期规则定义。
|
||||
|
||||
#### 设计决策
|
||||
|
||||
| 决策 | 选择 | 理由 |
|
||||
@@ -251,7 +311,7 @@ Physical Record 的顺序由 WAL 文件的顺序追加和顺序扫描保证,
|
||||
|
||||
##### WAL Batch
|
||||
|
||||
WAL Batch 是逻辑提交单元,对应一次 group commit batch。恢复时必须拼出完整 WAL Batch 后才能重放,不能重放半个 batch。
|
||||
WAL Batch 是物理持久化单元,对应一次 group commit batch。恢复时必须拼出完整 WAL Batch 后才能重放,不能重放半个 batch。WAL Batch 不是事务边界;一个 WAL Batch 可以包含多个独立 autocommit 写入,后续也可以包含一个或多个事务提交记录。
|
||||
|
||||
```text
|
||||
WAL Batch
|
||||
@@ -284,6 +344,37 @@ Entry 的 sequence 由 batch 内位置推导:
|
||||
entry[i].sequence = baseSequence + i
|
||||
```
|
||||
|
||||
###### 事务边界与 commit sequence
|
||||
|
||||
WAL 的 `baseSequence + i` 是物理 mutation sequence,用于保持 WAL 重放顺序和 `publishedSequence` 连续推进;它不能直接等同于多 key 事务的逻辑提交时间。
|
||||
|
||||
第一阶段单 key autocommit 规则:
|
||||
|
||||
```text
|
||||
每个 Put / Delete Entry 都是独立事务
|
||||
entry.sequence == commitSequence
|
||||
```
|
||||
|
||||
后续 MVCC / SSI 多 key 事务必须使用事务级提交记录,使同一事务内所有 mutation 共享同一个逻辑提交时间:
|
||||
|
||||
```text
|
||||
TxnCommit Record
|
||||
┌────────────┬────────────────┬───────────────┬───────────┐
|
||||
│ txnID │ commitSequence │ mutationCount │ mutations │
|
||||
│ bytes/var │ u64 │ u32 │ ... │
|
||||
└────────────┴────────────────┴───────────────┴───────────┘
|
||||
```
|
||||
|
||||
事务语义:
|
||||
|
||||
- `commitSequence` 是 MVCC reader 和 SSI 冲突检测使用的逻辑提交 timestamp
|
||||
- 同一 `TxnCommit` 内所有 mutation 对外必须以同一个 `commitSequence` 原子可见
|
||||
- 普通 reader 不得观察到同一事务的部分 mutation 已可见、部分 mutation 不可见
|
||||
- WAL 物理 sequence 只用于恢复顺序、幂等重放和 durable high-water mark,不作为多 key 事务的可见性边界
|
||||
- recovery 必须完整解析并校验一个 `TxnCommit` 后,才可以按同一个 `commitSequence` 重放其中所有 mutation
|
||||
|
||||
因此,单 key autocommit 可以继续使用当前 Entry 编码;多 key 事务在 WAL 格式扩展时必须增加事务提交记录,而不是把同一事务的多个 key 编码成多个独立 autocommit Entry。
|
||||
|
||||
##### Entry
|
||||
|
||||
Entry 使用 row-based 编码,每条 Entry 自带自己的 key/value 长度。`opType` 表示操作语义,`valueKind` 表示 value 的存储形态。
|
||||
@@ -317,6 +408,7 @@ Entry
|
||||
- `keyLen > 0`
|
||||
- `Put` 要求 `valueKind ∈ {Inline, ValueLogPointer}`
|
||||
- `Put + Inline` 允许 `valLen = 0`,表示 key 存在且 value 为空 bytes
|
||||
- 因为空 value 是合法值,API 层 `Get` 必须区分 key exists with empty value 与 key not found;否则 `ErrCommitUnknown` 后无法通过读取做弱状态确认
|
||||
- `Put + ValueLogPointer` 的 value 字段存 opaque encoded pointer bytes,具体格式由 Value Log 模块定义
|
||||
- `Delete` 编码为 tombstone,要求 `valueKind = None`、`valLen = 0` 且 value 为空
|
||||
- Delete 恢复到 MemTable 后写入 tombstone,而不是直接移除 key
|
||||
@@ -325,6 +417,22 @@ Entry
|
||||
|
||||
恢复目标是恢复到最后一个完整、CRC 校验通过、sequence 连续的 WAL Batch。恢复过程不能重放半个 batch;尾部半写可以截断,中间损坏必须报错。
|
||||
|
||||
###### CURRENT / MANIFEST 权威性
|
||||
|
||||
MANIFEST 是 recovery 起点和 checkpoint 状态的权威源,记录最老仍需恢复的 `recoverySegmentID`。CURRENT 只表示当前活跃 WAL segment,是写入侧快速定位 active segment 的辅助文件,不作为 recovery 起点的权威来源。
|
||||
|
||||
恢复时:
|
||||
|
||||
```text
|
||||
1. 读取 MANIFEST,取得 recoverySegmentID
|
||||
2. 从 WAL 目录扫描 segment 文件
|
||||
3. 从 recoverySegmentID 开始按 segmentID 升序恢复
|
||||
4. 如果 CURRENT 指向的 segment 与目录扫描结果不一致,以 MANIFEST + 目录中满足 header 校验的 segment 为准
|
||||
5. 如果 MANIFEST 指定的 recovery segment 缺失,或后续需要恢复的 segment 不连续,报错
|
||||
```
|
||||
|
||||
该规则保证即使 CURRENT 更新在崩溃前未持久化,恢复仍不会依赖不可靠的 active segment 指针;只要已确认写入所在 segment 已按 WAL 元数据持久化协议进入 durable-ready 状态,恢复就能通过目录扫描发现它。
|
||||
|
||||
###### Recovery 扫描流程
|
||||
|
||||
```text
|
||||
@@ -505,14 +613,15 @@ MemTable (可写, 64MB)
|
||||
|
||||
#### Flush 过滤规则
|
||||
|
||||
MemTable 可能包含已写入内存但尚未发布的 pending entry,也可能包含 WAL write / fsync 失败后保留的 aborted entry。Flush 到 SSTable 时必须过滤这些 entry:
|
||||
MemTable 可能包含已写入内存但尚未发布的 pending entry,也可能包含 fsync 失败后保留的 aborted entry。Flush 到 SSTable 时必须过滤这些 entry:
|
||||
|
||||
- 只刷 `sequence <= publishedSequence` 且非 aborted 的 entry
|
||||
- pending / unpublished entry 不进入 SSTable
|
||||
- aborted entry 不进入 SSTable
|
||||
- 失败 WAL Batch 中遗留在 MemTable 的 entry 只作为运行期内部状态存在,关闭后不会从 WAL 恢复
|
||||
- 失败 WAL Batch 中遗留在 MemTable 的 entry 只作为运行期内部状态存在,关闭后随内存丢弃,不会从 WAL 恢复
|
||||
- 含 pending / unpublished / aborted entry 的 MemTable 即使完成过滤 flush,也不得单独作为 checkpoint / MANIFEST 推进依据;推进规则必须遵守 3.2 的 write-stopped 后生命周期约束
|
||||
|
||||
该规则保证失败写入不会因为 MemTable flush 进入持久化 SSTable,也保持无锁读场景下无需原地删除 skiplist / arena 节点。
|
||||
该规则保证失败写入不会因为 MemTable flush 进入持久化 SSTable,也不会因为 checkpoint / MANIFEST 错误推进而被恢复路径当作已经持久化的连续 batch;同时保持无锁读场景下无需原地删除 skiplist / arena 节点。
|
||||
|
||||
#### 多 Immutable MemTable 策略
|
||||
|
||||
@@ -661,7 +770,7 @@ L3: 比 L2 大 10 倍,约 6.4GB
|
||||
↓
|
||||
value > 4KB?
|
||||
├── 是 → value 追加写入 Value Log,得到指针 [文件ID, 偏移, 长度]
|
||||
│ 默认 Always 策略下,Value Log record 必须先 fsync
|
||||
│ 默认 Always 策略下,该 WAL Batch 引用的 Value Log ranges 必须先通过 durable barrier
|
||||
│ WAL Batch 记录 Put + ValueLogPointer,value 字段存 encoded pointer
|
||||
│ MemTable 存:key → [vlog指针](pending / unpublished)
|
||||
│
|
||||
@@ -675,18 +784,21 @@ WAL Batch fsync 成功后发布 sequence,写入对普通读可见并返回成
|
||||
|
||||
Value Log 指针只有在被引用的 Value Log record 已满足当前落盘策略后,才能随 WAL Batch 发布。
|
||||
|
||||
默认 `Always` 策略下,顺序为:
|
||||
默认 `Always` 策略下,发布 WAL Batch 前必须先完成 Value Log durable barrier。该规则是有意的 durability-first 取舍:已确认写入恢复后不得出现悬空 Value Log pointer;大值写入吞吐通过 Value Log group commit、WAL group commit 和非默认落盘策略弥补,而不是削弱 `Always` 的恢复语义。
|
||||
|
||||
Value Log durable barrier 以 WAL Batch 引用的 Value Log ranges 为单位,而不是要求每条大 value 单独 fsync。实现可以在同一个 group commit 窗口内收集多条大 value append,先对这些 ranges 做一次 Value Log fsync,再写入并 fsync 对应 WAL Batch:
|
||||
|
||||
```text
|
||||
1. append Value Log record
|
||||
2. fsync Value Log record 所在文件
|
||||
3. 写入 WAL Batch(记录 key + vlog 指针)
|
||||
4. fsync WAL Batch
|
||||
5. 发布 sequence
|
||||
6. 返回写入成功
|
||||
1. 收集当前 group commit 窗口内的大 value 写入
|
||||
2. append 多条 Value Log record,得到各自的 ValueLogPointer
|
||||
3. fsync 这些 record 所在 Value Log 文件到 batch 引用的最大 offset
|
||||
4. 写入 WAL Batch(记录 key + vlog 指针)
|
||||
5. fsync WAL Batch
|
||||
6. 发布 sequence
|
||||
7. 返回写入成功
|
||||
```
|
||||
|
||||
如果 WAL 已持久化但 Value Log record 未持久化,恢复时会得到指向不存在或半写 value 的悬空指针,因此默认模式必须禁止这种状态。`Periodic` / `Never` 策略可以放宽 fsync 时机,但必须同时放宽 WAL 和 Value Log 的崩溃保证,并明确可能丢失最近已发布写入。
|
||||
如果 WAL 已持久化但 Value Log record 未持久化,恢复时会得到指向不存在或半写 value 的悬空指针,因此默认模式必须禁止这种状态。`Periodic` / `Never` 策略可以放宽 fsync 时机,但必须同时放宽 WAL 和 Value Log 的崩溃保证,并明确可能丢失最近已发布写入。把大 value 直接放入 WAL 可以避免外部指针依赖,但会破坏 KV 分离目标,增加 WAL 体积、恢复成本和写放大;第一版不作为默认方案。
|
||||
|
||||
WAL Entry 通过 `valueKind` 区分内联值和 Value Log 指针:
|
||||
|
||||
@@ -731,12 +843,13 @@ MemTable 找到 entry
|
||||
|
||||
#### 设计决策
|
||||
|
||||
| 决策 | 选择 | 理由 |
|
||||
| -------- | ------------------------------- | -------------------------------------------------- |
|
||||
| 分离阈值 | 4KB | 小值内联保范围扫描性能,大值分离保 compaction 效率 |
|
||||
| 文件格式 | 带校验的追加写 | CRC 校验防止磁盘损坏返回错误数据 |
|
||||
| 文件大小 | 固定 128MB | GC 粒度固定可预期,足够大不频繁切换 |
|
||||
| GC 策略 | 按文件存活率,低于 50% 触发重写 | 精准回收,只处理需要的文件 |
|
||||
| 决策 | 选择 | 理由 |
|
||||
| ---------- | ------------------------------- | ----------------------------------------------------------- |
|
||||
| 分离阈值 | 4KB | 小值内联保范围扫描性能,大值分离保 compaction 效率 |
|
||||
| fsync 策略 | group commit + durable barrier | 摊薄大 value fsync 成本,同时保持 `Always` 下无悬空指针 |
|
||||
| 文件格式 | 带校验的追加写 | CRC 校验防止磁盘损坏返回错误数据 |
|
||||
| 文件大小 | 固定 128MB | GC 粒度固定可预期,足够大不频繁切换 |
|
||||
| GC 策略 | 按文件存活率,低于 50% 触发重写 | 精准回收,只处理需要的文件 |
|
||||
|
||||
#### Value Log 记录格式
|
||||
|
||||
@@ -777,9 +890,9 @@ Value Log GC 计算存活率时,只能把已发布且非 aborted 的 LSM entry
|
||||
|
||||
以下模块尚未讨论,将在后续补充:
|
||||
|
||||
- **MVCC + 事务(SSI)** — 多版本并发控制,可串行化隔离级别
|
||||
- **MVCC + 事务(SSI)** — 多版本并发控制,可串行化隔离级别;必须提供 client-visible `txnID` / 幂等 token、durable commit record,以及 `CommitStatus(txnID)` 类提交状态查询能力
|
||||
- **范围扫描** — 多层 Merge Iterator
|
||||
- **网络层** — 自定义 TCP 二进制协议
|
||||
- **嵌入式 API** — Go 库接口设计
|
||||
- **嵌入式 API** — Go 库接口设计;`Get` 必须区分 key not found 与 key exists with empty value,写 API 必须明确 `ErrCommitUnknown` 的 maybe committed 语义和禁止盲目重试的约束
|
||||
- **崩溃恢复** — WAL 重放 + MANIFEST 恢复
|
||||
- **文件管理** — MANIFEST、CURRENT、文件生命周期
|
||||
|
||||
Reference in New Issue
Block a user