存储
4 KiB 页、WAL
与崩溃恢复。
存储路径是同步的。当前模型是单写者、STEAL、NO-FORCE,并由 WAL 保护。它支持同步物理运行时回滚与启动崩溃恢复。读没有快照,也可能看见活动写者。
Executor
↓
HeapStorage
↘ BTree persistence
↓
TransactionManager + WAL
↓
BufferPool + PageGuards
↓
PageManager
↓
Database file页格式 v5
数据页固定为 4096 字节。Page 0 仍为遗留容器 / 堆元数据,不按 Page v5 解释。堆元数据采用独立的 NBD1 版本 2 布局,并保存规范表指纹。版本 1 至 4 将被拒绝,不提供自动迁移。
| 偏移 | 字段 | 含义 |
|---|---|---|
| 0..4 | NBP1 | 页魔数 |
| 4..6 | u16 version | 格式版本 5 |
| 6 | u8 type | 2 Heap · 3 BTreeMeta · 4 Internal · 5 Leaf |
| 8..10 | slot count | 槽目录项数 |
| 10..14 | free bounds | 空闲区下界 / 上界 |
| 16..24 | pageLSN | 0 表示尚无 WAL 记录 |
| 24..28 | CRC32C | 绑定期望 PageId 的整页校验 |
| 28.. | slots | u16 offset + u16 length + u32 generation |
元组字节从页尾向前堆积。每个已分配槽都有非零 generation。保留对 (offset = 0, length = 65535) 表示 Deleted,并保留 generation。DELETE 压缩元组但不重编号槽;之后的插入可以在受检自增后复用最低合格墓碑。
RowId 是版本化物理定位符 PageId + SlotId + generation,不是业务键,也不会作为 SQL 列暴露。复用前,旧定位符报告 RowDeleted;复用后报告 StaleRowId,不能碰到新占用者。
缓冲池与写顺序
缓冲池拥有有界的原始页帧,使用简单轮转淘汰,在 guard 存活期间钉住页面,并拒绝淘汰被钉住的页。在写出脏数据页之前,它会先把 WAL 持久化到该页的 pageLSN。WAL flush 失败则不会尝试写数据页。
construct after-image with pageLSN
→ append PageUpdate
→ publish dirty buffer frame
→ flush WAL through pageLSN
→ write data pageWAL
每个数据库使用两个交替槽:<database>-wal 与 <database>-wal.next。逻辑 LSN 与物理偏移刻意分开:
logical LSN = generation base_lsn + (physical record offset - 48)
WAL 头是 48 字节,格式版本 3,并带整头 CRC32C。记录头 40 字节,格式版本 2;类型决定唯一合法总长。记录类型为 Begin、PageUpdate、Commit、Abort 与 RollbackComplete。PageUpdate 携带完整 4 KiB before / after 镜像。
结构完整但校验失败的记录是损坏,即使位于 EOF 也不会被当成崩溃尾截断。只有可用头通过结构校验的、物理不完整的最后一条记录,才可以在恢复边界丢弃。
事务与单写者
写者所有权由第一次写入惰性获取,只读事务不预定它。提交只在 Commit 记录到达持久存储后释放所有权。回滚先让 Abort 持久,再沿 prevLSN 链反向安装经验证的 before-image,然后持久记录 RollbackComplete。
丢弃未完成的脏写者不会隐式释放写所有权:后续写入将要求恢复,close 亦会返回错误。活动事务期间仍允许 flush,因为引擎采用 STEAL,并对每次页写进行 WAL 排序;flush 成功并不表示提交完成。
启动恢复
Open data file + WAL
→ Analysis
├── Winners (Commit exists)
├── Completed rollback (RollbackComplete)
└── Losers (incomplete or Abort-only)
→ Redo non-rolled-back PageUpdates in ascending LSN
→ Undo losers in descending global LSN
→ Sync undo + durably finalize recovered losers恢复在缓冲池对外暴露之前同步完成。pageLSN 仅在整页校验通过后可用于跳过 redo。校验失败的当前页为硬错误。恢复不会根据检查点可能已回收的 WAL 重建该页。
检查点
检查点是显式、同步、静止的。只要还有事务句柄、写者活动 / 挂起,或运行时健康要求启动恢复,就会返回类型化错误而不是等待。成功的检查点先刷 WAL,再按 WAL 顺序同步每个脏页,然后创建并同步下一代 WAL。有意省略干净关机标记:打开时扫描有界的当前代即可。
持久 B+Tree
堆页与 B+Tree 页共享同一个数据库文件、缓冲池、事务链、WAL、恢复趟与检查点。索引页是普通的、带校验的 Page v5,恰好一个 generation-1 的 payload 槽。
netbadb-index 拥有排序、节点与版本化编解码,不依赖存储、SQL 或执行器。BTreeHandle 是稳定的元数据页身份;根分裂可以替换根而不改变句柄。已注册索引由 create_index 回填,由堆与 SQL DML 维护,并作为 IndexScan 对规划器可见。不提供 SQL 索引 DDL。
完整性,不是认证
页 CRC 与 WAL CRC 检测持久损坏。它们既不修复损坏,也不提供密码学认证。解码 fuzz 覆盖 WAL 恢复、Page v5 与 B+Tree 节点。