build: 重建 dist(B-6 manifest 单一提交点 + LSM 结构根治 + 新错误码)

This commit is contained in:
thzxx
2026-09-15 10:29:13 +08:00
parent 81c46eb6f2
commit 18064594f5
12 changed files with 12137 additions and 3385 deletions
+2396 -675
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+151 -4
View File
@@ -1639,6 +1639,24 @@ declare class KVStoreEngine implements IStorageEngine {
private collectTableDiff;
}
/** v0.8.0:引擎恢复诊断(供上层展示/断言;不静默) */
interface AriaRecoveryReport {
/** 打开过程中被丢弃的 SSTable(按命名空间) */
droppedSSTables: {
namespace: string;
id: number;
level: number;
reason: string;
}[];
/** 是否怀疑已确认写入丢失(被丢弃的 SSTable 没有 WAL 兜底) */
dataLossSuspected: boolean;
/** WAL 活跃区间内的分片空洞(已提交事务记录缺失) */
walGaps: number[];
/** 本次打开是否从旧格式(__aria_lsm_meta/__aria_schemas)迁移而来 */
legacyImported: boolean;
/** 是否从更早的 manifest 世代回退(最新世代损坏) */
manifestFallback: boolean;
}
declare class AriaEngine implements IStorageEngine {
readonly name = "aria";
/**
@@ -1655,6 +1673,31 @@ declare class AriaEngine implements IStorageEngine {
private fileManager;
private bufferPool;
private dbLock;
/**
* v0.8.0(B-6):存储层单一提交点。
*
* 页面水位 / 各命名空间 SSTable 列表 / 表结构 / WAL 起始位置 / 待落盘冻结表意图
* 全部收敛到 `__aria_manifest_<generation>`:数据先落盘,再提交 manifest,
* **提交成功后**才允许截断 WAL 或删除旧分片。恢复只认最后一份 CRC 通过的世代。
*/
private manifestStore;
private manifest;
/**
* v0.8.0:已确认落盘的 WAL 水位(LSN)。
*
* 只在"所有 LSM 都没有未落盘数据"时推进到当前 LSN —— 于是
* `lsn <= durableLsn` 的记录必然已存在于已提交的 SSTable 中,
* 恢复时可以安全跳过(也就允许删除对应分片)。
*/
private durableLsn;
/**
* v0.8.0:仍需保留的最小 WAL 分片号(每次 `checkpointBefore` 的返回值)。
* 写进 manifest 的 `wal.startSegment`:即便分片删除只完成一半,恢复也只从
* 这个分片开始读,不会把上一世代的旧记录排到新记录之后重放。
*/
private walStartSegment;
/** v0.8.0:恢复诊断 */
private recoveryReport;
private schemas;
private tablePKs;
private opCounter;
@@ -1679,13 +1722,25 @@ declare class AriaEngine implements IStorageEngine {
* v0.4.2-fix: 崩溃恢复/自愈 — 校验并移除损坏 SSTable、截断 WAL、重建二级索引。
* v0.4.5 增强:清理 OPFS 残留临时文件、清理孤儿页面(meta 未引用的 pg_ 文件)。
* 应用层检测到异常后调用,无需删库重建。
*
* v0.8.0B-6):孤儿回收**必须**以"manifest 健康"为前提。
* 修复前 `cleanupOrphanPages` 只读裸 JSON meta,读不出来就当"没有任何引用",
* 于是元数据损坏时 repair 会把全部活页删掉(不可逆)。现在:
* - 只要本次打开出现过损坏世代/被丢弃的 SSTable → 直接跳过回收并告警;
* - 引用集合来自 manifest 的权威 meta 列表。
*/
repair(): Promise<void>;
/**
* v0.4.5: 清理孤儿页面 — 扫描全部 pg_* 文件,未被任何 LSM 命名空间 meta 引用的删除。
* v0.4.5: 清理孤儿页面 — 扫描全部 pg_* 文件,未被任何命名空间 meta 引用的删除。
* 孤儿页面来自:崩溃中断的 compaction/删除流程(旧 SSTable 页面残留)。
*
* v0.8.0B-6):引用集合取自 manifest;且**只有在没有损坏迹象时才执行** ——
* "任何引用不到的东西一律保留而非删除"在恢复路径上是不变量,只有显式 repair
* 且 manifest 完整可信时才允许回收空间。
*/
private cleanupOrphanPages;
/** v0.8.0: 读取某个 LSM 当前引用的层结构(诊断/孤儿回收用) */
private getLsmLevels;
/**
* v0.4.1: 重置数据库 — 清空全部数据与表结构(演示页刷新/重新初始化用)。
* 清空存储后端、LSM、WAL、MVCC 与二级索引,后续可继续使用本实例。
@@ -1797,15 +1852,107 @@ declare class AriaEngine implements IStorageEngine {
* v0.8.0(B-1):写入前置校验 —— 见 `IStorageEngine.validatePayload` 契约。
*/
validatePayload(tableName: string, rows: Record<string, unknown>[], mode?: 'insert' | 'update'): Promise<void>;
/**
* v0.8.0B-6):表结构**随 manifest 一起提交**(单一提交点)。
*
* 修复前 DDL 结束时会单独写一份 `__aria_schemas`:结构变更与存储状态
* SSTable meta / 页面 / WAL 水位)各自落盘,中间崩溃就会留下"结构说加过列、
* 数据里没有"或反之的分裂状态。现在两者在同一次原子提交里生效。
*/
private persistSchemas;
private loadSchemas;
/**
* 创建命名空间隔离的 SSTableStore
* v0.8.0: 恢复诊断(打开时被丢弃的 SSTable、WAL 空洞、是否怀疑数据丢失)
*
* 数据来自两处:引擎层(WAL 空洞 / 迁移 / manifest 回退)与各 LSM(被丢弃的
* SSTable + 其 `dataLossSuspected`),这里合并成一份对外的报告 —— 于是
* "这次打开到底自愈了什么、有没有真丢数据"是**可读的返回值**而不是只能翻日志。
*/
getRecoveryReport(): AriaRecoveryReport;
/**
* v0.8.0: LSM 的唯一构造点。
*
* 为什么集中:本项目最反复的缺陷模式就是"同一语义在多处实现、只修一处"
* (审计结论的原话)。主 LSM 与二级索引 LSM 的配置必须完全一致地带上
* 命名空间、WAL LSN 提供者与 durable-coverage 语义,因此只能有一个工厂。
*/
private createLSM;
/** 全部 LSM(主 + 二级索引) */
private allLsms;
/**
* v0.8.0: 把全部 LSM 的数据落盘。
* @param memtablesOnly true = 只落 memtable,不等 compactioncheckpoint 用)
*/
private flushAllLsms;
/** v0.8.0: 是否存在任何未落盘的 LSM 数据(决定 WAL 水位能否推进) */
private hasPendingFlushData;
/** schema 的持久化形态(与旧 `__aria_schemas` 同形) */
private serializeSchemas;
/** 所有 LSM 的待落盘冻结表意图(manifest 记录,阻止 WAL 水位越过它们) */
private collectFrozenIntents;
/**
* v0.8.0B-6):**唯一的 manifest 提交入口**。
*
* 每次提交都重新计算权威字段,因此并发/交错场景下"最后一次提交"总是包含
* 完整的最新状态:
* - `pageIdWatermark`:单调推进、永不复用;
* - `schemas`:表结构的权威描述(DDL 不再依赖独立的 `__aria_schemas` 提交);
* - `frozen`:待落盘冻结表意图;
* - `wal.startLsn`**只有全部数据已落盘时才推进**(否则保持原值),
* 这是"截断 WAL 前必须先提交 manifest"的可验证形式。
*/
private commitManifest;
/**
* v0.8.0:计算"当前可以保证的落盘水位"(纯函数,不改状态)。
*
* 三条规则:
* - 有冻结表意图 → 水位不得越过最早的冻结表内容起点(它们的记录只在内存+WAL);
* - 有未落盘 memtable → 维持原水位;
* - 全部落盘 → 推进到当前 LSN(这些记录已存在于已提交的 SSTable 中)。
*/
private computeDurableLsn;
/**
* v0.8.0B-6):WAL 检查点的**唯一实现** —— "先算边界 → 提交 manifest → 再删除"。
*
* 顺序不可交换(见 `SegmentedWALStore.planKeepFrom` 的说明)。所有需要回收 WAL
* 空间的路径(打开恢复后、close、repair、周期 checkpoint)都必须走这里,
* 否则又会出现"同一语义多处实现、只改一处"的老问题。
*/
private advanceWalCheckpoint;
/**
* v0.8.0:旧格式(v0.8.0 之前)状态导入。
*
* 导入必须是**全有或全无**的:
* - 旧 meta 存在但无法解析 → 抛错(`ARIA_LEGACY_META_CORRUPT`)。
* 修复前 `readMetaList()` 遇到坏 JSON 返回 `[]`,于是"元数据损坏"直接
* 表现为"空库",随后 repair 还会把没人引用的活页全部删掉(不可逆)。
*/
private importLegacyState;
/**
* v0.8.0:恢复后校验冻结表意图。
*
* 语义:manifest 记录了"某张冻结表还没落盘"(意图),说明它的数据要么在 WAL 里,
* 要么已经在 SSTable 里。若本次打开**一条 WAL 记录都没有重放到**,而 manifest
* 又声称有未落盘数据,那么这些"已确认写入"就是真的丢了(WAL 被截断/介质丢失)。
* 此时抛错 —— 修复前这种丢失完全不可观测。
*/
private verifyFrozenIntentsAfterRecovery;
/**
* 创建命名空间隔离的 SSTableStore**manifest 权威**)。
*
* 主 LSM 与每个二级索引 LSM 各持有独立实例:
* - 文件 key 前缀隔离(sst_ / sst_idx_${table}_${col}_
* - 元数据 key 隔离(__aria_lsm_meta / __aria_lsm_meta_${ns}
* - id 序列独立(避免 v0.2.4 共享 id 空间导致的文件互相覆盖)
* - 元数据在 manifest 的 `namespaces[ns]` 中隔离(不再各写一份裸 JSON
* - id 序列独立且**单调推进**`nextSstableId` 记在 manifest 里,
* 即便某一代 SSTable 全部被删除,id 也不会被复用)
*
* v0.8.0B-6)两处结构变化:
* 1. **meta 不再走裸 JSON**`saveMeta`/`deleteMeta` 直接改 manifest 并提交。
* 修复前 `JSON.parse` 失败 → 返回 `[]` → 元数据损坏 = 静默空库
* (随后 repair 还会把没人引用的活页删掉);
* 2. `load/delete` 的页面映射改由 PageSSTableStore 自己维护
* (活跃 + 退休两张表)—— 于是"compaction 摘除 meta"与
* "在途读者按 id 读取"不再互相矛盾。
*/
private createSSTableStore;
/** v0.4.5: 是否启用页面化物理存储(默认 OPFS / KVStore 后端启用,显式配置可覆盖) */
+2396 -675
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+2396 -675
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+2396 -675
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
Vendored
+2396 -675
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long