docs(B-6): 附录 H(B-6 完整实施记录)+ README/CHANGELOG/site/CONTRIBUTING 同步
- PLAN 附录 H:交付物清单、提交顺序不变量、**实施中新发现的 10 个缺陷**(含分片号复用、 读快照与并发 flush 的窗口、checkpoint 仍等 compaction、takeover 世代竞争、提交中冻结表 误报 WRITE_LOST、底部层只剩墓碑的 TypeError、介质读故障被当缺失、元数据损坏静默空库等), 以及可复现的验收命令与实测数字。 - PLAN 待办表:B-6 行改为"完整实施(非降级选项)";原"B-1 遗留"给出结论 (compaction/merge 输入只来自已校验数据,补校验反而有害;触发条件写明)。 - README:架构图/核心机制加入单一提交点;新增"存储布局在 v0.8.0 变更"的已知限制与迁移说明; 测试 1935 / 覆盖率 90.34 · 82.16 · 94.06 · 93.23;新增变异验证命令。 - CHANGELOG:0.8.0 条目补齐 B-6 完整实现(含 9 个新错误码与恢复报告)。 - site:错误码表补 7 个新码;AriaEngine 与崩溃恢复卡片按实现改写(不再宣称"空洞截断"); 首页徽章数字同步。 - CONTRIBUTING:新增"变异验证"一节(修复类提交必须能回答"回退后用例会不会失败")。
This commit is contained in:
+34
-3
@@ -11,8 +11,8 @@ All notable changes to MetonaSqlark will be documented in this file.
|
||||
> 必然复发的结构**:多处并存的语义实现被收敛为唯一实现,并第一次让崩溃语义、
|
||||
> 错误码一致性、入口等价性变成可机器验证的门禁。
|
||||
>
|
||||
> 测试规模 1304 → **1872(90 套件)+ 14 项 e2e**(另 4 个重型套件在独立 CI
|
||||
> job 串行运行)。
|
||||
> 测试规模 1304 → **1935(91 套件)+ 14 项 e2e**(另 4 个重型套件在独立 CI
|
||||
> job 串行运行);B-6 的全部修复另有 **22 项变异验证**(`scripts/mutation-b6.py`,全部通过)。
|
||||
|
||||
### 工作流 B · 结构根治(消除整类缺陷)
|
||||
|
||||
@@ -45,7 +45,38 @@ All notable changes to MetonaSqlark will be documented in this file.
|
||||
并统一分隔标识符语义:引号只在"解析→执行"边界脱去。
|
||||
顺带补上 **ORDER BY 的列存在性/歧义校验**(此前 JOIN 里裸写两表同名列既不报错
|
||||
也不确定按哪列排)。
|
||||
- **B-6 存储提交点(KVStore)** — 两处 P0:① `open()` 遇损坏日志尾部会**清空整个
|
||||
- **B-6 存储提交点(完整实施,非降级方案)** — 见 `PLAN-v0.7.5.md` 附录 H。
|
||||
- **`__aria_manifest_<generation>` 单一提交点**:页面水位 + 各命名空间 SSTable
|
||||
元数据 + 表结构 + WAL 起始位置 + 待落盘冻结表意图,一次原子提交
|
||||
(头部/载荷双 CRC、先写后验、保留两代)。顺序固定为
|
||||
**数据落盘 → manifest 提交 → 才允许截断 WAL / 删除旧文件**。
|
||||
- **元数据损坏不再静默空库**:此前 `__aria_lsm_meta*` 是裸 JSON,解析失败即
|
||||
`[]` → 看不到任何表,随后 `repair()` 还会把"没人引用"的活页删光(不可逆)。
|
||||
现在全部世代校验失败抛 `ARIA_MANIFEST_CORRUPT`,旧格式迁移遇到坏 JSON 抛
|
||||
`ARIA_LEGACY_META_CORRUPT`。
|
||||
- **WAL**:LSN 改为一库一条单调水位(manifest 记账);按水位删除旧分片;
|
||||
**分片号只增不减**(修复前全量截断后重置为 0,会与 manifest 记录的
|
||||
`startSegment` 错位,实测造成"删掉的行复活"与"已确认写入丢失"两个方向的损坏);
|
||||
分片空洞(含前缀缺失)显式报 `ARIA_WAL_GAP` 而不是静默丢弃尾部。
|
||||
- **LSM**:冻结表成为一等状态(失败可重试,`flush()` 先入链再报错,修复前
|
||||
一次后台失败会让之后每次 flush 直接抛错、数据永远等不到落盘);compaction
|
||||
不再"先摘整层再合并"(窗口内该层对读者不可见 → 少行);底部层原地合并
|
||||
**回收墓碑**;`compacting` 改为按层集合;被取代的 SSTable 进入退休表,
|
||||
等更早的读者退出才物理删除;`rangeScanLazy` 提前终止不再多算一条。
|
||||
- **读路径自洽**:删除引擎层全部 `prefetch*`/`drainChain` 依赖,改为
|
||||
"快照 + 结构版本乐观重试"(修复前那次改动会暴露一个新缺陷:并发 flush
|
||||
在扫描的 await 窗口里发布的 SSTable 对本次扫描不可见 → 刚改名的行读回旧值)。
|
||||
- **checkpoint 不等 compaction**:写路径的周期 checkpoint 只落 memtable,
|
||||
compaction 继续后台跑(v0.6.1 记录的 "8~11s 悬崖"的另一半)。
|
||||
- **介质故障与"文件不存在"分开**:读失败抛 `ARIA_SSTABLE_READ_FAILED`,
|
||||
不再被折叠成 null 从而误删元数据。
|
||||
- **恢复报告**:`engine.getRecoveryReport()` 返回
|
||||
`{droppedSSTables, dataLossSuspected, walGaps, legacyImported, manifestFallback}`。
|
||||
- 新增 `tests/v080-b6-single-commit-point.test.ts`(63 项:含 manifest 严格校验
|
||||
表驱动 25 例)与
|
||||
`scripts/mutation-b6.py`(22 项变异验证:把修复回退到修复前行为,对应用例
|
||||
必须失败 —— 全部被拦住)。
|
||||
- **B-6 存储提交点(KVStore 侧止血)** — 两处 P0:① `open()` 遇损坏日志尾部会**清空整个
|
||||
日志**(写 3 条 → 第 4 条撕裂 → 重开可见 → 再重开全空);② 自动 checkpoint
|
||||
失败会让**已确认写入**报错(而该写入已在 WAL 中,报错与事实相反)。另修陈旧实例
|
||||
的 checkpoint 会**静默抹掉**新实例写入(现抛 `STALE_INSTANCE` 拒绝提交)。
|
||||
|
||||
@@ -228,3 +228,21 @@ const db = await MetonaSqlark.create({
|
||||
## Questions?
|
||||
|
||||
Open an issue at [git.metona.cn/MetonaTeam/MetonaSqlark/issues](https://git.metona.cn/MetonaTeam/MetonaSqlark/issues).
|
||||
|
||||
---
|
||||
|
||||
## 变异验证(回归套件的"是否只是陪跑"检查)
|
||||
|
||||
修复类提交必须能回答一个问题:**把修复回退到修复前的行为,对应用例会不会失败?**
|
||||
不会失败的用例等于没有保护。
|
||||
|
||||
```bash
|
||||
# B-6(存储单一提交点 / LSM 结构根治)17 项变异验证
|
||||
python3 scripts/mutation-b6.py
|
||||
|
||||
# 只跑其中一条(按名字子串匹配)
|
||||
python3 scripts/mutation-b6.py "WAL 分片号复用"
|
||||
```
|
||||
|
||||
脚本会临时改写 `src/`、运行对应用例、再恢复源码(收到 SIGINT/SIGTERM 也会恢复),
|
||||
最后打印每一条是"被拦住"还是"仍然通过"。**出现任何一条"仍然通过",本次提交不算完成。**
|
||||
|
||||
+100
-6
@@ -804,7 +804,7 @@ connection-manager/integrations/migration)两份完整报告,以及测试质
|
||||
| ~~P1~~ | ~~A37 未限定列名 / WHERE 列存在性~~ | ✅ `567d150` |
|
||||
| ~~P1~~ | ~~A38/A39 压缩被遮蔽 / O(n²)~~ | ✅ `97b9fa4` |
|
||||
| ~~P1~~ | ~~A41 DDL 原子性~~ | ✅ `bf93ec5`(A40 经探针未复现,已固化为护栏) |
|
||||
| ~~P0~~ | ~~**B-6 存储单一提交点(manifest)**~~ | ✅ KVStore 三处止血(`edd9f1d`/`68e4731`)+ Aria DDL WAL 意图(`bf93ec5`) |
|
||||
| ~~P0~~ | ~~**B-6 存储单一提交点(manifest)**~~ | ✅ **完整实施**(非降级选项):`__aria_manifest` 单一提交点 + LSM 44/45/47/49/50/51/55 全部改造 + 引擎层 prefetch 依赖删除 —— 见**附录 H** |
|
||||
| ~~P1~~ | ~~**B-4 统一表达式求值器**~~ | ✅ `3983aae`(CASE 结构化解析 + 列引用/聚合统一) |
|
||||
| ~~P1~~ | ~~**B-5 输出列序号 + 分隔标识符**~~ | ✅ `2c945ee` |
|
||||
| ~~—~~ | ~~PC-2 e2e 真崩溃注入(CDP `Page.crash`)~~ | ✅ `6a0cfeb`(14 项 e2e,含两个 OPFS 写入窗口) |
|
||||
@@ -812,7 +812,7 @@ connection-manager/integrations/migration)两份完整报告,以及测试质
|
||||
| — | PG 文档与站点全量同步 | 待做 |
|
||||
|
||||
**下一步建议**:v0.8.0 的三条工作流(A 全部缺陷 / B-1~B-6 结构根治 / C 验证
|
||||
基础设施)与 G1~G6 门禁均已完成(见附录 G)。后续版本方向:
|
||||
基础设施)与 G1~G6 门禁均已完成(见附录 G、附录 H)。后续版本方向:
|
||||
|
||||
1. **真跨表快照** — `backup()` 目前是逐表读取(已如实写进已知限制)。真快照需要
|
||||
行所有权改为 COW(引擎内所有写入路径统一"新对象替换"而非就地修改),
|
||||
@@ -820,15 +820,24 @@ connection-manager/integrations/migration)两份完整报告,以及测试质
|
||||
2. **MVCC 真快照隔离** — `snapshotLsn`/`prevVersion` 目前只写不读,事务串行。
|
||||
若确有多事务并发需求,需让读路径走版本链而不是 `txnSnapshot`;
|
||||
3. **简单 CASE 形式** — `CASE <表达式> WHEN <值> THEN ...`(当前仅搜索式);
|
||||
4. **复合主键** — 存储布局与外键引用目前假设单主键;
|
||||
5. **B-1 遗留** — `AriaEngine` 的 compaction / merge 写入路径尚未过
|
||||
`validatePayload`(数据来自本引擎内部生成的 SSTable,非用户输入)。
|
||||
4. **复合主键** — 存储布局与外键引用目前假设单主键。
|
||||
|
||||
**关于原"B-1 遗留(compaction/merge 未过 validatePayload)"的结论**:该项**不是缺陷,
|
||||
也无需补校验**,理由是数据来源而非实现惰性 —— LSM 的 compaction/merge 输入只可能
|
||||
是本引擎**已提交的 SSTable**(`levels` 中的 meta 由本进程写入),其中每一行都曾在
|
||||
唯一写入 choke point(`Executor` → `validatePayload` → `compileValidator`)上校验过;
|
||||
compaction 做的是"逐字节搬运 + 按 key 去重",不解析、不构造新的行语义。
|
||||
在合并路径上再跑一遍校验反而有害:`ALTER TABLE` 之前写入的旧行形状与当前 schema
|
||||
可以合法地不同(这正是 ALTER 的语义),重校验会把合法历史数据判成非法而**中断
|
||||
compaction**。触发条件明确写在这里:**一旦 LSM 接受外部输入(例如导入第三方
|
||||
SSTable / 跨库搬迁),必须在 `save()` 之前补校验**。
|
||||
|
||||
**已完成的验证基础设施(本轮重点)**:
|
||||
- PC-2 真崩溃注入(CDP `Page.crash`)+ OPFS `createWritable` 的两个写入窗口;
|
||||
- `FaultyBackend` 字节级故障注入(撕裂/bit-flip/掉电/写失败);
|
||||
- **变异验证**成为回归套件的标准做法:把修复回退到修复前的行为,对应用例
|
||||
必须失败。本轮 A15/B-6(①②③)/A13/A38/A39 全部通过该检查 ——
|
||||
必须失败。本轮 A15/A13/A38/A39 与 **B-6 全量 17 项**均通过该检查(可执行脚本
|
||||
`scripts/mutation-b6.py`,逐项打印"变异是否被拦住")——
|
||||
这是"测试真能拦住回归"与"测试只是陪跑"的分界线。
|
||||
- 测试介质的两处**忠实性**缺陷已被修正(`SharedMemoryBackend` 跨实例可见性、
|
||||
OPFS mock 的目录语义)—— 它们此前让"多实例/多库"的验证跑在错误语义上。
|
||||
@@ -849,3 +858,88 @@ connection-manager/integrations/migration)两份完整报告,以及测试质
|
||||
| **G6** | 文档与实现一致:README/CHANGELOG/site 中每一条能力宣称都有对应测试或已删除 | §8 的 **12 条**全部收口:订阅(已接线)、LZ4 页面压缩(A38 已修)、`./migration` 导出、react/vue 产物+d.ts+peerDeps、`disconnect` 进类型、priority(改为实现符合文档)、MVCC 快照隔离(改为如实描述)、backup 一致性快照(改为如实描述并列入已知限制)、"5 种引擎"(改为 4 模式 + 3 后端)、`diskEngine` 生效范围(改为仅 aria)、"在线一致性快照"(改为全库导出)、钩子契约(CONTRIBUTING 写明返回值语义) | ✅ |
|
||||
|
||||
**额外要求**:CI lint 已去掉 `continue-on-error`(真正阻断);`tsconfig.test.json` 对 `tests/` 做类型检查并修复了 **103 个**被 babel 剥离类型掩盖的类型错误。 ✅
|
||||
|
||||
---
|
||||
|
||||
## 附录 H · B-6「存储层单一提交点」完整实施记录(v0.8.0 收敛)
|
||||
|
||||
> **为什么还需要这一节**:附录 F 的"已完成"表里 B-6 只列了 ①②③(KVStore 三处止血)+
|
||||
> Aria DDL WAL 意图,而 §B-6 的规格要求的是 **`__aria_manifest` 单一提交点 + LSM 单项改造**;
|
||||
> §"B-6 的降级选项"也白纸黑字写着那五处止血是"若周期不够"的替代品。
|
||||
> 本次(周期不受限、且明确禁止降级方案)把 B-6 **按规格完整实现**,本节记录落地细节、
|
||||
> 实施中新发现的 10 个缺陷,以及可复现的验证方式。
|
||||
|
||||
### H.1 交付物
|
||||
|
||||
| 文件 | 作用 |
|
||||
|---|---|
|
||||
| `src/engine/aria/store/manifest.ts`(新增) | `__aria_manifest_<generation>`:magic + formatVersion + generation + 头部 CRC + 载荷 CRC;`ManifestStore.load/claimOwnership/commit`;先写后验;保留两代;损坏世代**显式失败** |
|
||||
| `src/engine/aria/index/lsm.ts` | 冻结表一等状态(id/LSN/重试/提交窗口);读路径 epoch + 结构版本重试;compaction 不再摘整层、底部层原地合并回收墓碑、按层 `compacting` 集合;flush/maintenance 双链;恢复报告 |
|
||||
| `src/engine/aria/index.ts` | manifest 装载 → 旧格式迁移 → 认领所有权;`commitManifest()`(唯一提交入口);`advanceWalCheckpoint()`(先算边界 → 提交 → 再删除);表结构随 manifest 提交;恢复报告 `getRecoveryReport()`;删除引擎层全部 `prefetch*` 依赖 |
|
||||
| `src/engine/aria/wal/log.ts` / `segmented_store.ts` | LSN 全库单调(manifest 记高水位);`planKeepFrom` / `truncateBefore`;**分片号只增不减**;空洞(含前缀缺失)如实上报;按水位清理旧格式键 |
|
||||
| `src/engine/aria/store/page_sstable_store.ts` | 活跃 + 退休两张页面映射(在途读者按 id 读取不再被 compaction 摘除影响);`registerPageIds` |
|
||||
| `src/engine/aria/store/file_manager.ts` | 页面水位取 manifest / 旧 meta / 现存最大 id 三者最大(单调、永不复用);`__aria_meta` 降级为兼容提示 |
|
||||
| `src/engine/aria/index/sstable.ts` | 三份解析循环合并为 `iterEntries()` 一份,越界策略统一 |
|
||||
| `src/engine/aria/index/merge_iterator.ts` | 胜出来源的补充推迟到下一次 `next()`(提前终止不多算) |
|
||||
| `src/engine/aria/wal/checkpoint.ts` | checkpoint 只落 memtable(`flushMemtables?`),不等 compaction |
|
||||
| `tests/v080-b6-single-commit-point.test.ts`(新增 63 项) | 上述每一条的回归 + manifest 严格校验表驱动 25 例;`scripts/mutation-b6.py`(22 项变异验证,全部被拦住) |
|
||||
|
||||
### H.2 提交顺序(唯一不变量)
|
||||
|
||||
```
|
||||
数据落盘(SSTable 页面 / 整 value 已 await 完成)
|
||||
↓
|
||||
__aria_manifest_<gen> 提交(单文件原子写 + 回读校验)
|
||||
↓
|
||||
才允许截断 WAL / 删除旧分片 / 删除旧 SSTable
|
||||
```
|
||||
|
||||
- manifest 载荷 = `{pageIdWatermark, namespaces{SSTable meta + nextSstableId}, schemas,
|
||||
wal{startSegment,startLsn,nextLsn}, frozen[冻结表意图], owner{instanceId,epoch}, committedAt}`
|
||||
- 恢复只认**最后一份 CRC 通过**的世代;存在 manifest 文件但全部世代无效 → `ARIA_MANIFEST_CORRUPT`
|
||||
(修复前:裸 JSON meta 解析失败 → `[]` → **静默空库**,随后 repair 还会把"没人引用"的活页删光)
|
||||
- `frozen` 意图的作用:`wal.startLsn` 不得越过最早冻结表的 `lsnAtFreeze`;
|
||||
恢复时若"manifest 声称有未落盘数据 + 一条 WAL 记录都没重放到" → `ARIA_WRITE_LOST`
|
||||
(把"已确认写入是否真的丢了"从不可观测变成可判定)
|
||||
|
||||
### H.3 实施中新发现的缺陷(方案未列出,已一并修复)
|
||||
|
||||
| # | 现象(实测) | 根因 | 修复 |
|
||||
|---|---|---|---|
|
||||
| 1 | **删掉的行复活 / 已确认写入丢失**(随机压力套件:76≠68、53≠55 两个方向都出现) | WAL 全量截断后 `currentSegment` 重置为 0,而 manifest 里的 `startSegment` 是删除前算好的(= 旧最大号+1)→ 新记录写进被跳过的分片号;分片号复用还让同一序号对应两代记录,恢复按序号排序时旧 INSERT 排在 DELETE 之后 | 分片号**只增不减**(`truncateBefore`/`truncate` 都只往上走;只有整库清空 `clearAll` 才 `reset()`);正则放宽到 `\d{6,}` |
|
||||
| 2 | 300 行全部改名后,紧接着的全表扫描有 25 行仍是旧值(几毫秒后又变新值) | 读路径"同步取 meta 快照 → await 加载",而并发 flush 会在这个窗口里把新 SSTable 发布到 `levels[0]` **并同时**从 `frozenMemtables` 摘掉 → 该批数据既不在快照里也不在前台 | `structureVersion` + 乐观重试(加载后版本变了就重来;极端情况退回"等两条链静默") |
|
||||
| 3 | `close()` 之后 `__wal_000000.bin` 仍在(每次打开都要重读一遍) | `planKeepFrom` 只在"下一条记录也在水位之内"时才敢删边界分片,而**最后一个分片**没有"下一片"可比 → 永不删除 | `truncateBefore` 接受 `latestLsn`:末片以"当前 LSN ≤ 水位"判定整片可删 |
|
||||
| 4 | 第二个实例打开同一库时 `open()` 直接失败(两个实例互相判 STALE_INSTANCE) | 认领所有权只 +1 个世代号,旧实例**在途**的提交正好落在同一个号上并覆盖认领 | 认领时一次跨过 `MANIFEST_TAKEOVER_STRIDE=1000` 个世代号 |
|
||||
| 5 | 从上一代 manifest 回退时报 `ARIA_WRITE_LOST`(数据其实完好) | `saveMeta` 正在提交这张冻结表,而 `frozen` 意图仍把它列为"未落盘" | `FrozenTable.committing` 窗口:提交中的表由本次提交负责,不进意图;失败时标志复位、可重试 |
|
||||
| 6 | 底部层"整层只剩墓碑"时 compaction 抛 `TypeError`(读 `merged[0][0]`)→ 该层永远无法合并、墓碑永远回收不掉 | 空产物没有专门的退休分支 | 新增"全部是墓碑 → 直接退休旧文件"分支(`retireMetas`) |
|
||||
| 7 | item 55 实际未生效:checkpoint 仍然等整条 compaction | `CheckpointManager.checkpoint()` 里还有一句 `await this.lsm.flush()`(会 drain 维护链) | `Flushable.flushMemtables?()` + 引擎提供 `flushAllLsms(true)`;旧调用方保留原语义 |
|
||||
| 8 | 一个损坏的更高世代把库**永久锁死**(之后每次提交都 STALE_INSTANCE) | 陈旧检查拿"磁盘上最大世代号"与自己的世代比,损坏世代不是"别人的提交" | 陈旧判定跳过**已知损坏**的世代;但新世代号仍严格大于任何已存在文件(绝不覆盖) |
|
||||
| 9 | 旧格式迁移遇到坏 JSON 静默当空库 | `catch { return [] }`(meta 与 schema 各一处) | `ARIA_LEGACY_META_CORRUPT` 显式失败(迁移全有或全无) |
|
||||
| 10 | 介质读故障被折叠成"文件不存在",于是 `dropInvalidSSTable` 把 meta 删掉(不可逆) | `load()` 的 catch 一律 `return null`,调用方无法区分"读失败"与"确实没有" | 读失败**向上抛** `ARIA_SSTABLE_READ_FAILED`;只有"确定不存在且仍被 manifest 引用"才自愈清理,并进恢复报告 |
|
||||
|
||||
### H.4 验收(可复现)
|
||||
|
||||
```bash
|
||||
# 1) B-6 回归(63 项)
|
||||
npx jest tests/v080-b6-single-commit-point.test.ts
|
||||
|
||||
# 2) 变异验证:把每个修复回退到修复前行为,对应用例必须失败(22 项)
|
||||
python3 scripts/mutation-b6.py
|
||||
|
||||
# 3) 随机压力 / 检查点崩溃恢复(发现缺陷 1 的套件)
|
||||
npx jest tests/engine/aria-repair-hardening.test.ts tests/engine/aria-kv-backend.test.ts
|
||||
|
||||
# 4) 全量常规套件(不含重型与 e2e)
|
||||
npx jest --testPathIgnorePatterns='/node_modules/|/tests/e2e/|aria-prod-load|kvstore-stress|aria-matrix-audit|aria-idx-flush-race'
|
||||
```
|
||||
|
||||
**实测数字**:常规套件 **1935 通过 / 91 套件**;覆盖率 语句 90.34% / 分支 82.16% /
|
||||
函数 94.06% / 行 93.23%(阈值 90/82/94/93,全部通过)。
|
||||
|
||||
**恢复报告**:`engine.getRecoveryReport()` 返回
|
||||
`{droppedSSTables, dataLossSuspected, walGaps, legacyImported, manifestFallback}` ——
|
||||
"损坏被自愈了多少、有没有真正丢数据"从此是**可读的返回值**,而不是只能翻日志。
|
||||
|
||||
**新增错误码**:`ARIA_MANIFEST_CORRUPT`、`ARIA_MANIFEST_WRITE_FAILED`、
|
||||
`ARIA_MANIFEST_NOT_LOADED`、`ARIA_LEGACY_META_CORRUPT`、`ARIA_SSTABLE_READ_FAILED`、
|
||||
`ARIA_SSTABLE_SAVE_CONTRACT`、`ARIA_WAL_GAP`、`ARIA_WRITE_LOST`、`STALE_INSTANCE`。
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/version-0.8.0-blue?style=flat-square" alt="version">
|
||||
<img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="license">
|
||||
<img src="https://img.shields.io/badge/coverage-90.43%25%20stmts-brightgreen?style=flat-square" alt="coverage">
|
||||
<img src="https://img.shields.io/badge/tests-1872%20passed-success?style=flat-square" alt="tests">
|
||||
<img src="https://img.shields.io/badge/coverage-90.34%25%20stmts-brightgreen?style=flat-square" alt="coverage">
|
||||
<img src="https://img.shields.io/badge/tests-1935%20passed-success?style=flat-square" alt="tests">
|
||||
</p>
|
||||
|
||||
> 基于 TypeScript 的**前端关系型数据库**:完整 SQL + Query Builder 双 API,
|
||||
@@ -356,6 +356,8 @@ const rows = await db.query('SELECT * FROM users');
|
||||
│ 快照回滚 │ 二级索引 LSM │ +大小头 │
|
||||
│ +Savepoint │ (每列独立) │ │
|
||||
├────────────────┴───────────────┴────────────┤
|
||||
│ __aria_manifest 单一提交点(数据→提交→截断)│
|
||||
├──────────────────────────────────────────────┤
|
||||
│ EncryptedBackend (AES-256-GCM 全库透明加密) │
|
||||
├──────────────────────────────────────────────┤
|
||||
│ 存储后端: OPFS Backend / KVStore Backend │
|
||||
@@ -369,8 +371,10 @@ const rows = await db.query('SELECT * FROM users');
|
||||
|------|------|
|
||||
| **LSM-Tree** | MemTable(红黑树)→ 多级 SSTable,异步 Compaction(从存储兜底加载),写背压 |
|
||||
| **页面化存储** | SSTable 存为 4KB 页面(FileManager 分配 pageId + BufferPool LRU 缓存 256 页 ≈ 1MB),`pageStorage` 在 opfs/kv 后端默认启用 |
|
||||
| **WAL** | 分片文件 `__wal_%06d.bin` + 真追加;标准 CRC32 记录校验;full/batch/none 三模式;空洞检测截断;16MB 阈值自动 checkpoint(活跃事务期间不截断) |
|
||||
| **崩溃恢复** | 打开时完整性校验(损坏 SSTable 自愈清理 + 整文件 CRC-32)、WAL 恢复、恢复后自动重建二级索引;`repair()` 清理孤儿页面与残留 |
|
||||
| **WAL** | 分片文件 `__wal_%06d.bin` + 真追加;标准 CRC32 记录校验;full/batch/none 三模式;**LSN 全库单调**(manifest 记高水位);分片号只增不减;空洞(含前缀缺失)显式上报 `ARIA_WAL_GAP`;16MB 阈值自动 checkpoint(活跃事务期间不截断) |
|
||||
| **单一提交点** | `__aria_manifest_<gen>`:页面水位 + 各命名空间 SSTable 元数据 + 表结构 + WAL 起始位置 + 待落盘冻结表意图,一次原子提交(头部/载荷双 CRC,先写后验,保留两代)。顺序固定为**数据落盘 → manifest 提交 → 才允许截断 WAL / 删除旧文件**;恢复只认最后一份 CRC 通过的世代,元数据损坏抛 `ARIA_MANIFEST_CORRUPT`(不再静默当空库) |
|
||||
| **崩溃恢复** | 打开时完整性校验(整文件 CRC-32;**介质读故障不再被当成"文件不存在"**,抛 `ARIA_SSTABLE_READ_FAILED` 且不误删元数据)、按 LSN 水位重放 WAL、恢复后自动重建二级索引;`getRecoveryReport()` 返回 `{droppedSSTables, dataLossSuspected, walGaps, legacyImported, manifestFallback}`;`repair()` 只在 manifest 健康时回收孤儿页面 |
|
||||
| **Compaction** | 整层合并不再"先摘层再合并"(合并期间该层对读者始终可见);底部层原地合并**回收墓碑**(删除密集场景空间不再无界增长);按层 `compacting` 集合(跨层触发不丢失);被取代的 SSTable 进入**退休表**,等更早的读者退出后才物理删除 |
|
||||
| **全库加密** | `encryption.password` → EncryptedBackend 透明加解密(WAL/SSTable/Schema/元数据全密文);PBKDF2 派生 + salt 持久化;密码错误/篡改 → `ARIA_DECRYPT_ERROR` |
|
||||
| **MVCC** | 版本链仅作事务内 undo(提交即清理,**无快照隔离**;事务串行);自动 GC |
|
||||
| **二级索引** | 每列独立 LSM Tree,支持等值/范围扫描,跨重启恢复,WAL 恢复后自动重建 |
|
||||
@@ -418,7 +422,8 @@ const { data, loading, error, refresh } = useSqlarkQuery(db, 'SELECT * FROM user
|
||||
npm install # 安装依赖
|
||||
npm run dev # 开发模式(localhost:3001)
|
||||
npm run build # 生产构建(生成 dist/)
|
||||
npm test # 运行测试(1872 用例 · 90 套件;+4 个重型套件)
|
||||
npm test # 运行测试(1935 用例 · 91 套件;+4 个重型套件)
|
||||
python3 scripts/mutation-b6.py # 变异验证:把 B-6 的修复逐项回退,对应用例必须失败
|
||||
npm run test:e2e # Playwright e2e(真实 Chromium + OPFS + 崩溃注入,需先 build)
|
||||
npm run lint # 代码检查
|
||||
npm run typecheck # 类型检查
|
||||
@@ -430,11 +435,11 @@ npm run typecheck # 类型检查
|
||||
|
||||
| 指标 | 数值 |
|
||||
|------|------|
|
||||
| 测试用例 | 1872(90 套件)+ 14 Playwright e2e,另 4 个重型套件在独立 CI job 串行运行 |
|
||||
| 语句覆盖率 | 90.43%(7835/8664) |
|
||||
| 分支覆盖率 | 82.21%(4092/4977) |
|
||||
| 函数覆盖率 | 94.27%(1103/1170) |
|
||||
| 行覆盖率 | 93.44%(7103/7601) |
|
||||
| 测试用例 | 1935(91 套件)+ 14 Playwright e2e,另 4 个重型套件在独立 CI job 串行运行 |
|
||||
| 语句覆盖率 | 90.34%(8393/9290) |
|
||||
| 分支覆盖率 | 82.16%(4367/5315) |
|
||||
| 函数覆盖率 | 94.06%(1205/1281) |
|
||||
| 行覆盖率 | 93.23%(7607/8159) |
|
||||
| SQL 关键字 | 72 |
|
||||
| 存储模式 | 4(`memory` / `disk` / `hybrid` / `aria`) |
|
||||
| 存储后端 | 3(OPFS / KVStore / Memory),Aria 引擎另有 LSM-Tree + WAL + 页面化 |
|
||||
@@ -455,6 +460,11 @@ npm run typecheck # 类型检查
|
||||
|
||||
### 已知限制(v0.8.0)
|
||||
|
||||
- **存储布局在 v0.8.0 变更** — 元数据从"每个命名空间一份裸 JSON"(`__aria_lsm_meta*` /
|
||||
`__aria_schemas`)收敛为 `__aria_manifest_<generation>`(带世代号与双 CRC)。
|
||||
旧库**首次用 v0.8.0 打开时自动迁移**(旧键保留不删,可回退旧版本),迁移遇到损坏
|
||||
的旧元数据会明确报 `ARIA_LEGACY_META_CORRUPT` 而不是当成空库。直接读取这些内部
|
||||
key 的外部脚本需要跟着改(引擎侧无公开 API 依赖它们)。
|
||||
- **单列主键** — 复合主键暂不支持(建表时显式 `SCHEMA_ERROR`),列入 v0.8 路线图
|
||||
- **写语句关联引用** — UPDATE/DELETE 的 WHERE 支持非关联子查询(`IN (SELECT)` / 标量子查询),关联引用(`$col` / 关联 EXISTS)显式抛 `NOT_SUPPORTED`(不静默)
|
||||
- **`backup()` 不是跨表一致性快照** — 实现为**逐表读取**(Aria 走引擎级 `backup()`,
|
||||
|
||||
@@ -920,6 +920,13 @@ db.<span class="f">broadcastChange</span>(<span class="s">'users'</span>);</pre>
|
||||
<tr><td><code>QUERY_ERROR</code></td><td>未知 where 操作符(如拼错的 <code>$betwen</code>)🆕 v0.7.2</td></tr>
|
||||
<tr><td><code>COLUMN_NOT_FOUND</code></td><td>列不存在:UPDATE 未知列 / ALTER DROP 不存在列(v0.7.4 UPDATE 未知列显式报错)</td></tr>
|
||||
<tr><td><code>INDEX_NOT_FOUND</code></td><td>DROP 不存在的索引</td></tr>
|
||||
<tr><td><code>ARIA_MANIFEST_CORRUPT</code></td><td>存储元数据(manifest)全部世代校验失败:拒绝打开,而不是当成空库 🆕 v0.8.0</td></tr>
|
||||
<tr><td><code>ARIA_MANIFEST_WRITE_FAILED</code></td><td>manifest 提交后回读校验失败(提交未生效,内存态不前进)🆕 v0.8.0</td></tr>
|
||||
<tr><td><code>ARIA_LEGACY_META_CORRUPT</code></td><td>旧格式(v0.8.0 之前)元数据损坏,无法安全迁移 🆕 v0.8.0</td></tr>
|
||||
<tr><td><code>ARIA_SSTABLE_READ_FAILED</code></td><td>介质读故障(区别于"文件不存在":不删元数据、不回退成静默空结果)🆕 v0.8.0</td></tr>
|
||||
<tr><td><code>ARIA_WAL_GAP</code></td><td>WAL 分片空洞(含前缀缺失):拒绝在"少了一段日志"的情况下静默继续 🆕 v0.8.0</td></tr>
|
||||
<tr><td><code>ARIA_WRITE_LOST</code></td><td>manifest 声称有未落盘数据,但 WAL 中没有任何可重放的记录(已确认写入确实丢失)🆕 v0.8.0</td></tr>
|
||||
<tr><td><code>STALE_INSTANCE</code></td><td>陈旧实例拒绝提交(另一个实例已提交更新的世代),不会静默覆盖 🆕 v0.8.0</td></tr>
|
||||
</table>
|
||||
|
||||
</main>
|
||||
|
||||
+3
-3
@@ -153,7 +153,7 @@
|
||||
<!-- Hero -->
|
||||
<section class="hero">
|
||||
<div class="container">
|
||||
<div class="badge" style="margin-bottom:24px;"><span class="dot"></span> v0.8.0 根治性迭代 — 1872 测试 90 套件 · 语句/分支/函数/行覆盖率 90.43% / 82.21% / 94.27% / 93.44% · UPDATE/DELETE 子查询正确执行 · 主键非空强制 · DROP INDEX 保留 UNIQUE 约束 · findStream 真惰性 · 参数化查询 · 自研 KVStore 事务引擎 · AriaEngine LSM+WAL+MVCC · 崩溃恢复</div>
|
||||
<div class="badge" style="margin-bottom:24px;"><span class="dot"></span> v0.8.0 根治性迭代 — 1935 测试 91 套件 · 语句/分支/函数/行覆盖率 90.34% / 82.16% / 94.06% / 93.23% · UPDATE/DELETE 子查询正确执行 · 主键非空强制 · DROP INDEX 保留 UNIQUE 约束 · findStream 真惰性 · 参数化查询 · 自研 KVStore 事务引擎 · AriaEngine LSM+WAL+MVCC · 崩溃恢复</div>
|
||||
<h1>前端的 <span class="gradient-text">SQL 数据库</span></h1>
|
||||
<p>TypeScript 原生构建,5 种存储引擎,支持完整 SQL 查询。<br>零运行时依赖,开箱即用。AriaEngine 自研引擎:LSM-Tree + WAL 同步 + MVCC。</p>
|
||||
<div class="actions">
|
||||
@@ -244,7 +244,7 @@ npm install @metona-team/metona-sqlark
|
||||
<div class="feature-card">
|
||||
<div class="icon">🌲</div>
|
||||
<h3>AriaEngine <span style="font-size:0.65rem;color:var(--accent);vertical-align:super;">v0.8.0</span></h3>
|
||||
<p>自研 LSM-Tree 存储引擎:SSTable 4KB 页面化物理存储(BufferPool LRU)、WAL 分片文件原子写入、全库 AES-GCM 透明加密、标准 CRC-32 完整性校验、MVCC 快照隔离、二级索引跨重启恢复、ON UPDATE/DELETE 外键级联、崩溃恢复自愈。</p>
|
||||
<p>自研 LSM-Tree 存储引擎:SSTable 4KB 页面化物理存储(BufferPool LRU)、WAL 分片文件原子写入、<strong>__aria_manifest 单一提交点</strong>(数据落盘 → 元数据原子提交 → 才截断 WAL)、全库 AES-GCM 透明加密、标准 CRC-32 完整性校验、MVCC 快照回滚、二级索引跨重启恢复、ON UPDATE/DELETE 外键级联、崩溃恢复自愈。</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="icon">🔒</div>
|
||||
@@ -264,7 +264,7 @@ npm install @metona-team/metona-sqlark
|
||||
<div class="feature-card">
|
||||
<div class="icon">🛡</div>
|
||||
<h3>崩溃恢复自愈</h3>
|
||||
<p>异常退出后无需删库重建:整文件 CRC-32 校验 + 打开自动跳过损坏 SSTable,db.repair() 清理损坏数据/孤儿页面/残留文件并重建索引,db.clearAll() 重置。迁移版本持久化,重启不重跑。</p>
|
||||
<p>异常退出后无需删库重建:整文件 CRC-32 校验 + 打开自动跳过损坏 SSTable,并按 WAL 水位重放已提交事务;<code>getRecoveryReport()</code> 如实给出被丢弃的 SSTable、WAL 空洞与是否怀疑丢数据。介质<strong>读故障</strong>与"文件不存在"分开处理(前者抛 ARIA_SSTABLE_READ_FAILED,绝不误删元数据);元数据整体损坏时显式拒绝打开(ARIA_MANIFEST_CORRUPT),而不是当成空库。db.repair() 只在 manifest 健康时回收孤儿页面并重建索引。</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="icon">🏊</div>
|
||||
|
||||
Reference in New Issue
Block a user