update WAL segment size 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:
+57
-1
@@ -116,7 +116,7 @@
|
||||
|
||||
默认模式下,写入成功发生在 WAL fsync 之后;但 fsync 的粒度是 WAL Batch,不是单条 `Put`。因此多条写入共享一次 fsync 成本,同时每条写入仍然只有在自身所在 WAL Batch 持久化后才算成功。
|
||||
|
||||
步骤 ④ 和 ⑤ 是 WAL 副作用之前的强制资源关口:WAL writer 必须先执行 WAL Batch 资源上限校验,再按 Batch 内所有 entry 的最大内存占用(key、value 或 ValueLogPointer、skiplist 节点、arena 对齐与层高开销)计算需要预留的 Arena 字节数。若 Batch 超过 WAL 资源上限,或单个 Batch 的最大可能内存占用超过 MemTable 上限,该 Batch 必须在 sequence 分配和 WAL write 之前按普通错误拒绝。若当前可写 MemTable 剩余容量不足,必须先把它冻结为 Immutable MemTable,并创建新的可写 MemTable 后再继续。若 Immutable MemTable 队列已达上限,该 Batch 必须在 WAL write 之前等待后台 flush 释放容量。实现不得在 WAL write / append 已尝试之后,再因为 Batch 超限或 Arena 满而让 MemTable pending 写入失败。
|
||||
步骤 ④ 和 ⑤ 是 WAL 副作用之前的强制资源关口:WAL writer 必须先执行 WAL Batch 资源上限校验,确认 encoded batch 在当前配置下可以写入一个空 WAL segment,再按 Batch 内所有 entry 的最大内存占用(key、value 或 ValueLogPointer、skiplist 节点、arena 对齐与层高开销)计算需要预留的 Arena 字节数。若 Batch 超过 WAL 资源上限、超过空 WAL segment 可承载上限,或单个 Batch 的最大可能内存占用超过 MemTable 上限,该 Batch 必须在 sequence 分配和 WAL write 之前按普通错误拒绝。若当前可写 MemTable 剩余容量不足,必须先把它冻结为 Immutable MemTable,并创建新的可写 MemTable 后再继续。若 Immutable MemTable 队列已达上限,该 Batch 必须在 WAL write 之前等待后台 flush 释放容量。实现不得在 WAL write / append 已尝试之后,再因为 Batch 超限、segment 容量不可能容纳或 Arena 满而让 MemTable pending 写入失败。
|
||||
|
||||
步骤 ⑧ 与 ⑩ 之间存在明确的内存序约束:MemTable skiplist / arena 节点必须先通过原子发布机制写入读路径可见结构(例如 `atomic.Pointer` store-release,或等价的 release publish),并且 Batch 内所有 entry 的节点都完成发布后,才能用 `atomic.Uint64.Store` 推进 `publishedSequence`。普通读者必须先 `Load` 当前 `publishedSequence`,再遍历 MemTable;读到 entry 后仍以 `entry.sequence <= loadedPublishedSequence` 判断可见性。该顺序保证弱内存序架构上读者不会先观察到已推进的 `publishedSequence`,却看不到对应已发布的 skiplist 节点。
|
||||
|
||||
@@ -226,6 +226,7 @@ MANIFEST 不在每次 WAL segment 轮转时更新。MANIFEST 表示 recovery 起
|
||||
| 批量窗口 | 时间 + 大小双触发(500µs 或 32KB) | 高负载靠大小触发效率最大化,低负载靠时间触发不会卡住 |
|
||||
| 日志格式 | 固定 Block(32KB) | 恢复时按 block 读取校验,比逐条快;和 SSTable block 设计一致 |
|
||||
| 文件管理 | 分段日志 | 精确删除已刷盘的旧 WAL 段;恢复通过 MANIFEST + WAL 目录扫描发现 segment;CURRENT 仅作为 last-known active segment 的 best-effort hint |
|
||||
| WAL segment 大小 | 默认 64MB,可配置但有最小值约束 | 保证最大合法 WAL Batch 可以写入空 segment,同时避免过小 segment 导致频繁 rotation |
|
||||
|
||||
#### WAL Block 格式
|
||||
|
||||
@@ -336,6 +337,61 @@ Physical Record 的顺序由 WAL 文件的顺序追加和顺序扫描保证,
|
||||
- 不允许先写入 Batch 的部分 fragment,再因 segment 空间不足切换 segment
|
||||
- Recovery 的 fragment 收集状态不跨 segment 携带;每个非最后恢复 segment 扫描结束时必须处于 `Idle`
|
||||
|
||||
`maxWalSegmentSize` 是单个 WAL segment 文件的最大字节数,包含 WAL File Header;`maxWalSegmentPayload` 是 header 之后可用于 Block / Physical Record / WAL Batch 的 payload 区大小。Segment rotation 的容量判断必须基于 payload 区剩余空间,而不是整个文件大小。
|
||||
|
||||
配置必须满足以下不变量,确保最大合法 WAL Batch 可以写入一个空 segment:
|
||||
|
||||
```text
|
||||
maxWalSegmentPayload = maxWalSegmentSize - walFileHeaderSize
|
||||
|
||||
maxWalSegmentPayload >=
|
||||
maxEncodedWalBatchSize
|
||||
+ worstCasePhysicalRecordOverhead
|
||||
+ worstCaseBlockPadding
|
||||
```
|
||||
|
||||
当前格式下:
|
||||
|
||||
```text
|
||||
walFileHeaderSize = 32 bytes
|
||||
walBlockSize = 32KB
|
||||
physicalRecordHeaderSize = 7 bytes
|
||||
walBatchHeaderSize = 18 bytes
|
||||
maxWalBatchEntriesSize = 4MB
|
||||
|
||||
maxEncodedWalBatchSize =
|
||||
walBatchHeaderSize + maxWalBatchEntriesSize
|
||||
= 18 + 4,194,304
|
||||
= 4,194,322 bytes
|
||||
|
||||
maxPhysicalRecordPayload =
|
||||
walBlockSize - physicalRecordHeaderSize
|
||||
= 32,768 - 7
|
||||
= 32,761 bytes
|
||||
|
||||
maxPhysicalRecordCount =
|
||||
ceil(maxEncodedWalBatchSize / maxPhysicalRecordPayload)
|
||||
= 129
|
||||
|
||||
worstCasePhysicalRecordOverhead =
|
||||
maxPhysicalRecordCount * physicalRecordHeaderSize
|
||||
= 129 * 7
|
||||
= 903 bytes
|
||||
|
||||
worstCaseBlockPadding = 7 bytes
|
||||
|
||||
minWalSegmentPayload =
|
||||
4,194,322 + 903 + 7
|
||||
= 4,195,232 bytes
|
||||
|
||||
minWalSegmentSize =
|
||||
walFileHeaderSize + minWalSegmentPayload
|
||||
= 32 + 4,195,232
|
||||
= 4,195,264 bytes
|
||||
```
|
||||
|
||||
因此,在当前默认资源上限下,`maxWalSegmentSize` 必须 `>= 4,195,264 bytes`。默认 `maxWalSegmentSize = 64MB`,满足该约束。`maxWalSegmentSize`、`maxWalBatchEntriesSize`、`walBlockSize` 必须在 DB 创建 / 打开时用 checked arithmetic 联合校验;如果配置无法满足上述不变量,DB 必须拒绝启动或拒绝创建,而不是运行到写入阶段才发现某个合法 Batch 无法写入空 segment。
|
||||
|
||||
##### WAL Batch
|
||||
|
||||
WAL Batch 是物理持久化单元,对应一次 group commit batch。恢复时必须拼出完整 WAL Batch 后才能重放,不能重放半个 batch。WAL Batch 不是事务边界;一个 WAL Batch 可以包含多个独立 autocommit 写入,后续也可以包含一个或多个事务提交记录。
|
||||
|
||||
Reference in New Issue
Block a user