存储

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..4NBP1页魔数
4..6u16 version格式版本 5
6u8 type2 Heap · 3 BTreeMeta · 4 Internal · 5 Leaf
8..10slot count槽目录项数
10..14free bounds空闲区下界 / 上界
16..24pageLSN0 表示尚无 WAL 记录
24..28CRC32C绑定期望 PageId 的整页校验
28..slotsu16 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 page

WAL

每个数据库使用两个交替槽:<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 节点。