fix(P0): 生产矩阵审计暴露 2 个崩溃恢复丢数据 bug — ① FileManager 页面 id 崩溃回退(allocatePageIds 后 saveMeta 前中断 → nextPageId 回退 → 恢复期页面复用被 compaction 误删,kv 2万行+加密崩溃丢75%):init 以现存最大页面 id+1 为准;② LSM.flush 剩余数据与 compaction 并发写 meta(产物 saveMeta 被覆盖成孤儿 → 优雅关闭重开丢75%):剩余落盘挂链串行。新增 13 组合矩阵审计(后端×特性×功能全量验收)
This commit is contained in:
@@ -573,15 +573,21 @@ export class LSM {
|
||||
}
|
||||
// v0.4.3-fix: 循环等待级联任务(flush 完成可能触发新的 compaction)
|
||||
await this.drainChain();
|
||||
// 若仍有 frozen 数据未刷盘,在链尾追加
|
||||
// v0.6.1-fix(P0): 剩余数据 flush 必须挂链串行 —— 此前直接 await 执行,
|
||||
// 与链上 compaction 并发写 SSTable meta:compaction 产物 saveMeta 后,
|
||||
// memtable flush 的 saveMeta 读到中间态列表(含 compaction 产物)→ 覆盖产物
|
||||
// 引用 → compaction 产物变孤儿 → 索引/主表数据静默丢失(优雅关闭后重开丢 75%)。
|
||||
// 挂链后按序执行(memtable flush 在 compaction 之后),meta 无竞态。
|
||||
if (this.immutableMemtable) {
|
||||
const frozen = this.immutableMemtable;
|
||||
await this.flushImmutableAsync(frozen);
|
||||
this.flushChain = this.enqueueOnChain(() => this.flushImmutableAsync(frozen));
|
||||
}
|
||||
if (this.memtable.getEntryCount() > 0) {
|
||||
this.freezeMemtable();
|
||||
const frozen = this.immutableMemtable;
|
||||
if (frozen) await this.flushImmutableAsync(frozen);
|
||||
if (frozen) {
|
||||
this.flushChain = this.enqueueOnChain(() => this.flushImmutableAsync(frozen));
|
||||
}
|
||||
}
|
||||
this.frozenMemtables = [];
|
||||
// 刷盘完成后级联调度可能触发 compaction → 排空到稳定
|
||||
|
||||
@@ -28,11 +28,24 @@ export class FileManager implements PageIO {
|
||||
async init(dbName: string): Promise<void> {
|
||||
this.dbName = dbName;
|
||||
const meta = await this.backend.read('__aria_meta');
|
||||
let nextPageId = 1;
|
||||
if (meta && meta instanceof ArrayBuffer && meta.byteLength >= 4) {
|
||||
const view = new DataView(meta);
|
||||
this.nextPageId = view.getUint32(0, false);
|
||||
} else {
|
||||
this.nextPageId = 1;
|
||||
nextPageId = new DataView(meta).getUint32(0, false);
|
||||
}
|
||||
// v0.6.1-fix(P0): 崩溃一致性 —— allocatePageIds 的 saveMeta 可能在分配
|
||||
// 页面后被崩溃中断(nextPageId 回退)。恢复时若按回退值继续分配,
|
||||
// 页面 id 复用会覆盖旧页面;随后 compaction 按 meta.pageIds 删除"旧"SSTable
|
||||
// 时误删被复用的新数据 → 崩溃恢复后静默丢数据(kv 后端 2 万行丢 75%)。
|
||||
// 修复:以"现存最大页面 id + 1"为准(单调不回退,绝不复用已存在页面)。
|
||||
const keys = await this.backend.listKeys();
|
||||
for (const k of keys) {
|
||||
if (k.startsWith('pg_')) {
|
||||
const id = Number.parseInt(k.slice(3), 10);
|
||||
if (!Number.isNaN(id) && id + 1 > nextPageId) nextPageId = id + 1;
|
||||
}
|
||||
}
|
||||
this.nextPageId = nextPageId;
|
||||
if (!meta || !(meta instanceof ArrayBuffer) || meta.byteLength < 4 || nextPageId !== new DataView(meta as ArrayBuffer).getUint32(0, false)) {
|
||||
await this.saveMeta();
|
||||
}
|
||||
this.metaLoaded = true;
|
||||
|
||||
Reference in New Issue
Block a user