release: v0.4.3 — 关闭时序与后台任务加固(close 排空、失败不吞错、compaction 竞态修复)+ 提交先 WAL
CI / test (18.x) (push) Successful in 10m6s
CI / test (20.x) (push) Successful in 10m11s
CI / test (22.x) (push) Successful in 10m4s
CI / test (24.x) (push) Successful in 10m0s

This commit is contained in:
thzxx
2026-08-09 19:48:06 +08:00
parent 22b0b1fad4
commit d269bdfb75
23 changed files with 964 additions and 360 deletions
+177 -85
View File
@@ -34,7 +34,7 @@ class DatabaseError extends Error {
// ---------------------------------------------------------------------------
// 版本
// ---------------------------------------------------------------------------
const VERSION = '0.4.2';
const VERSION = '0.4.3';
/**
* metona-sqlark Shared WHERE Matcher 统一的条件匹配逻辑
@@ -1021,6 +1021,13 @@ class IndexedDBEngine {
}
}
async close() {
// v0.4.3-fix: 活跃事务时先回滚(避免 commit 对已关闭连接报错)
if (this.txActive) {
try {
await this.rollbackTransaction();
}
catch { /* 回滚失败不阻塞关闭 */ }
}
if (this.db) {
this.db.onversionchange = null; // 清理监听器
this.db.close();
@@ -1607,6 +1614,18 @@ class OPFSEngine {
this.dbName = '';
// 运行时内存缓存(OPFS 文件读写有延迟)
this.memoryCache = new MemoryEngine();
/**
* v0.4.3-fix: 写操作串行队列 内存写 + 快照 + 文件持久化整体排队执行
* close() 等待队列排空后再释放目录句柄避免 close 后挂起写泄漏/读旧数据
* 前一个操作失败不阻塞后续错误仍返回给调用方
*/
this.opQueue = Promise.resolve();
}
/** 将写操作加入串行队列(快照在队列内取,始终最新) */
enqueueOp(fn) {
const run = this.opQueue.then(fn, fn);
this.opQueue = run.then(() => undefined, () => undefined);
return run;
}
// ---- 生命周期 ----
async open(dbName, version) {
@@ -1620,6 +1639,11 @@ class OPFSEngine {
await this.loadExistingTables();
}
async close() {
// v0.4.3-fix: 等待所有挂起写操作完成(否则 close 后写仍在进行 → 重启读旧数据)
try {
await this.opQueue;
}
catch { /* 写失败已返回给调用方 */ }
this.root = null;
this.tablesDir = null;
await this.memoryCache.close();
@@ -1630,12 +1654,22 @@ class OPFSEngine {
// ---- v0.4.2-fix: 自愈 / 重置 / 元数据 ----
/** 自愈:重置内存缓存后从 OPFS 重新加载(单文件损坏不影响其他表) */
async repair() {
// v0.4.3-fix: 先等写队列排空(避免与挂起写竞态)
try {
await this.opQueue;
}
catch { /* ignore */ }
await this.memoryCache.close();
await this.memoryCache.open(this.dbName, 1);
await this.loadExistingTables();
}
/** 清空全部数据与表结构(删除目录内全部文件) */
async clearAll() {
// v0.4.3-fix: 先等写队列排空
try {
await this.opQueue;
}
catch { /* ignore */ }
await this.memoryCache.close();
await this.memoryCache.open(this.dbName, 1);
if (this.tablesDir) {
@@ -1670,29 +1704,33 @@ class OPFSEngine {
}
// ---- 表管理 ----
async createTable(schema) {
await this.memoryCache.createTable(schema);
// v0.4.2-fix: schema 持久化(此前仅写空数据文件 → 空表重启后消失、索引标记丢失)
await this.setMeta(`schema_${schema.name}`, JSON.stringify(schema));
// OPFS 中表以空 JSON 数组文件形式存在
await this.writeTableData(schema.name, []);
return this.enqueueOp(async () => {
await this.memoryCache.createTable(schema);
// v0.4.2-fix: schema 持久化(此前仅写空数据文件 → 空表重启后消失、索引标记丢失)
await this.setMeta(`schema_${schema.name}`, JSON.stringify(schema));
// OPFS 中表以空 JSON 数组文件形式存在
await this.writeTableData(schema.name, []);
});
}
async dropTable(tableName) {
await this.memoryCache.dropTable(tableName);
// v0.4.2-fix: 清理 schema meta(否则重启恢复幽灵表)
if (this.tablesDir) {
try {
await this.tablesDir.removeEntry(`__metona_schema_${tableName}.meta`);
return this.enqueueOp(async () => {
await this.memoryCache.dropTable(tableName);
// v0.4.2-fix: 清理 schema meta(否则重启恢复幽灵表)
if (this.tablesDir) {
try {
await this.tablesDir.removeEntry(`__metona_schema_${tableName}.meta`);
}
catch {
// 文件不存在则忽略
}
try {
await this.tablesDir.removeEntry(`${tableName}.json`);
}
catch {
// 文件不存在则忽略
}
}
catch {
// 文件不存在则忽略
}
try {
await this.tablesDir.removeEntry(`${tableName}.json`);
}
catch {
// 文件不存在则忽略
}
}
});
}
async hasTable(tableName) {
if (!this.tablesDir)
@@ -1721,20 +1759,24 @@ class OPFSEngine {
}
/** v0.4.2-fix: 引擎级 ALTER TABLE — 内存 + schema 持久化 + 整表文件重写 */
async alterTable(tableName, action, column) {
await this.memoryCache.alterTable(tableName, action, column);
const schema = await this.memoryCache.getTableSchema(tableName);
if (schema)
await this.setMeta(`schema_${tableName}`, JSON.stringify(schema));
const rows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, rows);
return this.enqueueOp(async () => {
await this.memoryCache.alterTable(tableName, action, column);
const schema = await this.memoryCache.getTableSchema(tableName);
if (schema)
await this.setMeta(`schema_${tableName}`, JSON.stringify(schema));
const rows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, rows);
});
}
// ---- CRUD ----
async insert(tableName, rows) {
const pks = await this.memoryCache.insert(tableName, rows);
// 持久化到 OPFS
const allRows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, allRows);
return pks;
return this.enqueueOp(async () => {
const pks = await this.memoryCache.insert(tableName, rows);
// 持久化到 OPFS(快照在队列内取,始终最新)
const allRows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, allRows);
return pks;
});
}
async find(tableName, query) {
return this.memoryCache.find(tableName, query);
@@ -1744,50 +1786,62 @@ class OPFSEngine {
return this.memoryCache.findStream(tableName, query, onRow);
}
async update(tableName, query, updates) {
const count = await this.memoryCache.update(tableName, query, updates);
const allRows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, allRows);
return count;
return this.enqueueOp(async () => {
const count = await this.memoryCache.update(tableName, query, updates);
const allRows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, allRows);
return count;
});
}
async delete(tableName, query) {
const count = await this.memoryCache.delete(tableName, query);
const allRows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, allRows);
return count;
return this.enqueueOp(async () => {
const count = await this.memoryCache.delete(tableName, query);
const allRows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, allRows);
return count;
});
}
async count(tableName, query) {
return this.memoryCache.count(tableName, query);
}
async clear(tableName) {
await this.memoryCache.clear(tableName);
await this.writeTableData(tableName, []);
return this.enqueueOp(async () => {
await this.memoryCache.clear(tableName);
await this.writeTableData(tableName, []);
});
}
// ---- 动态索引(v0.3.0 ----
async createIndex(tableName, column, unique) {
await this.memoryCache.createIndex(tableName, column, unique);
// v0.4.2-fix: 索引标记持久化(重启后索引结构恢复)
const schema = await this.memoryCache.getTableSchema(tableName);
if (schema)
await this.setMeta(`schema_${tableName}`, JSON.stringify(schema));
return this.enqueueOp(async () => {
await this.memoryCache.createIndex(tableName, column, unique);
// v0.4.2-fix: 索引标记持久化(重启后索引结构恢复)
const schema = await this.memoryCache.getTableSchema(tableName);
if (schema)
await this.setMeta(`schema_${tableName}`, JSON.stringify(schema));
});
}
async dropIndex(tableName, column, indexName) {
await this.memoryCache.dropIndex(tableName, column, indexName);
const schema = await this.memoryCache.getTableSchema(tableName);
if (schema)
await this.setMeta(`schema_${tableName}`, JSON.stringify(schema));
return this.enqueueOp(async () => {
await this.memoryCache.dropIndex(tableName, column, indexName);
const schema = await this.memoryCache.getTableSchema(tableName);
if (schema)
await this.setMeta(`schema_${tableName}`, JSON.stringify(schema));
});
}
// ---- 事务 ----
async beginTransaction() {
await this.memoryCache.beginTransaction();
}
async commitTransaction() {
await this.memoryCache.commitTransaction();
// 将内存数据刷到 OPFS
const tableNames = await this.memoryCache.getTableNames();
for (const tableName of tableNames) {
const rows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, rows);
}
return this.enqueueOp(async () => {
await this.memoryCache.commitTransaction();
// 将内存数据刷到 OPFS
const tableNames = await this.memoryCache.getTableNames();
for (const tableName of tableNames) {
const rows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, rows);
}
});
}
async rollbackTransaction() {
await this.memoryCache.rollbackTransaction();
@@ -1800,6 +1854,7 @@ class OPFSEngine {
return this.tablesDir;
}
async writeTableData(tableName, data) {
// 由 enqueueOp 串行化调用,此处直接写文件
const dir = this.ensureDir();
const fileName = `${tableName}.json`;
const fileHandle = await dir.getFileHandle(fileName, { create: true });
@@ -3274,6 +3329,11 @@ class LSM {
this.compacting = false; // 防止重复触发 compaction
/** 串行化 flush/compaction 链:保证持久化顺序与 id 分配顺序一致 */
this.flushChain = Promise.resolve();
/**
* v0.4.3-fix: 最近一次后台 flush/compaction 失败
* 后台失败不卡死链吞错防死锁但在显式 flush()/close() 时报告不静默
*/
this.lastBackgroundError = null;
this.memtableSizeThreshold = config.memtableSizeThreshold ?? DEFAULT_MEMTABLE_SIZE;
this.memtable = new MemTable(this.memtableSizeThreshold);
this.levelSizeMultiplier = config.levelSizeMultiplier ?? DEFAULT_LEVEL_SIZE_MULTIPLIER;
@@ -3371,11 +3431,26 @@ class LSM {
return this.flushChain
.then(task)
.catch((error) => {
// v0.4.3-fix: 记录失败(flush()/close() 时报告),不再完全静默吞错
this.lastBackgroundError = error;
// eslint-disable-next-line no-console
console.warn('[AriaEngine LSM] background flush/compaction failed:', error);
// catch 返回 undefined → 链恢复为 resolved,后续任务继续
});
}
/**
* v0.4.3-fix: 排空后台链 循环等待 flushChain 直到稳定
* 任务完成时可能级联调度新任务compaction 多级触发单次 await 等不到
* close/flush/clear 必须等待全部后台任务完成后才能安全关闭底层存储
*/
async drainChain() {
while (true) {
const chain = this.flushChain;
await chain;
if (this.flushChain === chain)
return;
}
}
/** 将指定 Immutable MemTable 刷盘为 SSTableid 由 store 按命名空间分配) */
async flushImmutableAsync(frozen) {
const entries = frozen.getAllEntries();
@@ -3415,27 +3490,27 @@ class LSM {
this.scheduleCompact(0);
}
}
/** 异步调度 compaction,使用 setTimeout 分片执行 */
/**
* 异步调度 compaction
* v0.4.3-fix: 去掉 setTimeout 分片 此前未触发的定时器在 close 后执行
* 用已关闭的 backend 写存储错误被吞 close reopen 时旧闭包引用新 backend 交叉污染
* 现在直接挂在 flushChain 串行执行close drainChain 能等到全部完成
*/
scheduleCompact(level) {
if (level >= MAX_LSM_LEVELS - 1 || this.compacting)
return;
this.compacting = true;
setTimeout(() => {
try {
this.flushChain = this.enqueueOnChain(() => this.compactLevelAsync(level));
this.flushChain = this.enqueueOnChain(() => this.compactLevelAsync(level).finally(() => {
this.compacting = false;
// 连续触发:如果 compaction 后仍然超标,继续调度
if (this.levels[level].length >= 4) {
this.scheduleCompact(level);
}
finally {
this.compacting = false;
// 连续触发:如果 compaction 后仍然超标,继续调度
if (this.levels[level].length >= 4) {
this.scheduleCompact(level);
}
// 检查下一级是否需要 compaction
if (level + 1 < MAX_LSM_LEVELS - 1 && this.levels[level + 1].length >= 4) {
this.scheduleCompact(level + 1);
}
// 检查下一级是否需要 compaction
if (level + 1 < MAX_LSM_LEVELS - 1 && this.levels[level + 1].length >= 4) {
this.scheduleCompact(level + 1);
}
}, 0);
}));
}
/** 背压场景下排队 compaction(写入路径调用) */
enqueueCompact(level) {
@@ -3451,6 +3526,8 @@ class LSM {
}
}).catch((error) => {
this.compacting = false;
// v0.4.3-fix: 记录失败(flush()/close() 时报告)
this.lastBackgroundError = error;
// eslint-disable-next-line no-console
console.warn('[AriaEngine LSM] background compaction failed:', error);
});
@@ -3461,10 +3538,11 @@ class LSM {
/**
* 预加载指定 key 范围内可能命中的所有 SSTable 到缓存
* 在同步扫描/查找之前调用保证 loadSSTableReader 不会因缓存未命中而返回 null
* 先等待 flush 链完成避免 flush 的缓存裁剪与预加载竞争驱逐刚加载的 SSTable
* v0.4.3-fix: 等待链稳定drainChain 后台 flush/compaction 在链上动态增长
* 单次 await compaction 仍可能合并 levels导致扫描时新 meta 缓存未命中而跳块丢数据
*/
async prefetchRange(startKey, endKey) {
await this.flushChain;
await this.drainChain();
this.trimCache();
const toLoad = [];
for (let level = 0; level < MAX_LSM_LEVELS; level++) {
@@ -3483,7 +3561,8 @@ class LSM {
async prefetchKeys(keys) {
if (keys.length === 0)
return;
await this.flushChain;
// v0.4.3-fix: 等待链稳定(同 prefetchRange,防 compaction 竞态丢数据)
await this.drainChain();
this.trimCache();
const toLoad = new Set();
for (let level = 0; level < MAX_LSM_LEVELS; level++) {
@@ -3674,8 +3753,14 @@ class LSM {
}
/** 等待所有排队的 flush/compaction 完成,并将剩余数据刷盘 */
async flush() {
// 等待链上已排队的 flush/compaction
await this.flushChain;
// v0.4.3-fix: 报告后台失败(消费一次,不永久吞错)
if (this.lastBackgroundError !== null) {
const error = this.lastBackgroundError;
this.lastBackgroundError = null;
throw new DatabaseError('AriaEngine background flush/compaction failed (data may be inconsistent)', 'ARIA_BACKGROUND_ERROR', error);
}
// v0.4.3-fix: 循环等待级联任务(flush 完成可能触发新的 compaction
await this.drainChain();
// 若仍有 frozen 数据未刷盘,在链尾追加
if (this.immutableMemtable) {
const frozen = this.immutableMemtable;
@@ -3688,8 +3773,12 @@ class LSM {
await this.flushImmutableAsync(frozen);
}
this.frozenMemtables = [];
// 刷盘完成后级联调度可能触发 compaction → 排空到稳定
await this.drainChain();
}
async clear() {
// v0.4.3-fix: 清空前排空后台任务(避免 compaction 在清空后写回残留 meta/数据)
await this.drainChain();
this.memtable.clear();
this.immutableMemtable = null;
this.frozenMemtables = [];
@@ -6304,6 +6393,16 @@ class AriaEngine {
async commitTransaction() {
if (!this.currentTxnId)
throw new DatabaseError('No active transaction', 'TX_NONE');
// v0.4.3-fix: 先持久化 WAL COMMIT,再合并快照到 LSM —
// 崩溃在 WAL 提交后、快照合并前:恢复时 WAL 重放数据,重启一致;
// 崩溃在 WAL 提交前:commitTransaction 尚未返回,事务视为未提交(可回滚)
await this.wal.append({
type: WALRecordType.COMMIT,
txnId: this.currentTxnId,
tableName: '',
key: '',
});
await this.wal.flush();
if (this.txnSnapshot) {
for (const [key, value] of this.txnSnapshot) {
if (value.__txn_deleted) {
@@ -6315,15 +6414,8 @@ class AriaEngine {
}
}
this.mvcc.commitTransaction(this.currentTxnId);
await this.wal.append({
type: WALRecordType.COMMIT,
txnId: this.currentTxnId,
tableName: '',
key: '',
});
this.currentTxnId = null;
this.txnSnapshot = null;
await this.wal.flush();
}
async rollbackTransaction() {
if (!this.currentTxnId)
+1 -1
View File
File diff suppressed because one or more lines are too long
+9 -1
View File
@@ -118,7 +118,7 @@ interface MetonaPlugin {
/** 销毁 */
destroy(): void;
}
declare const VERSION = "0.4.2";
declare const VERSION = "0.4.3";
/**
* metona-sqlark Plugin — 插件系统
@@ -853,6 +853,14 @@ declare class OPFSEngine implements IStorageEngine {
private tablesDir;
private dbName;
private memoryCache;
/**
* v0.4.3-fix: 写操作串行队列 — 内存写 + 快照 + 文件持久化整体排队执行,
* close() 等待队列排空后再释放目录句柄(避免 close 后挂起写泄漏/读旧数据)。
* 前一个操作失败不阻塞后续(错误仍返回给调用方)。
*/
private opQueue;
/** 将写操作加入串行队列(快照在队列内取,始终最新) */
private enqueueOp;
open(dbName: string, version: number): Promise<void>;
close(): Promise<void>;
isOpen(): boolean;
+177 -85
View File
@@ -30,7 +30,7 @@ class DatabaseError extends Error {
// ---------------------------------------------------------------------------
// 版本
// ---------------------------------------------------------------------------
const VERSION = '0.4.2';
const VERSION = '0.4.3';
/**
* metona-sqlark Shared WHERE Matcher 统一的条件匹配逻辑
@@ -1017,6 +1017,13 @@ class IndexedDBEngine {
}
}
async close() {
// v0.4.3-fix: 活跃事务时先回滚(避免 commit 对已关闭连接报错)
if (this.txActive) {
try {
await this.rollbackTransaction();
}
catch { /* 回滚失败不阻塞关闭 */ }
}
if (this.db) {
this.db.onversionchange = null; // 清理监听器
this.db.close();
@@ -1603,6 +1610,18 @@ class OPFSEngine {
this.dbName = '';
// 运行时内存缓存(OPFS 文件读写有延迟)
this.memoryCache = new MemoryEngine();
/**
* v0.4.3-fix: 写操作串行队列 内存写 + 快照 + 文件持久化整体排队执行
* close() 等待队列排空后再释放目录句柄避免 close 后挂起写泄漏/读旧数据
* 前一个操作失败不阻塞后续错误仍返回给调用方
*/
this.opQueue = Promise.resolve();
}
/** 将写操作加入串行队列(快照在队列内取,始终最新) */
enqueueOp(fn) {
const run = this.opQueue.then(fn, fn);
this.opQueue = run.then(() => undefined, () => undefined);
return run;
}
// ---- 生命周期 ----
async open(dbName, version) {
@@ -1616,6 +1635,11 @@ class OPFSEngine {
await this.loadExistingTables();
}
async close() {
// v0.4.3-fix: 等待所有挂起写操作完成(否则 close 后写仍在进行 → 重启读旧数据)
try {
await this.opQueue;
}
catch { /* 写失败已返回给调用方 */ }
this.root = null;
this.tablesDir = null;
await this.memoryCache.close();
@@ -1626,12 +1650,22 @@ class OPFSEngine {
// ---- v0.4.2-fix: 自愈 / 重置 / 元数据 ----
/** 自愈:重置内存缓存后从 OPFS 重新加载(单文件损坏不影响其他表) */
async repair() {
// v0.4.3-fix: 先等写队列排空(避免与挂起写竞态)
try {
await this.opQueue;
}
catch { /* ignore */ }
await this.memoryCache.close();
await this.memoryCache.open(this.dbName, 1);
await this.loadExistingTables();
}
/** 清空全部数据与表结构(删除目录内全部文件) */
async clearAll() {
// v0.4.3-fix: 先等写队列排空
try {
await this.opQueue;
}
catch { /* ignore */ }
await this.memoryCache.close();
await this.memoryCache.open(this.dbName, 1);
if (this.tablesDir) {
@@ -1666,29 +1700,33 @@ class OPFSEngine {
}
// ---- 表管理 ----
async createTable(schema) {
await this.memoryCache.createTable(schema);
// v0.4.2-fix: schema 持久化(此前仅写空数据文件 → 空表重启后消失、索引标记丢失)
await this.setMeta(`schema_${schema.name}`, JSON.stringify(schema));
// OPFS 中表以空 JSON 数组文件形式存在
await this.writeTableData(schema.name, []);
return this.enqueueOp(async () => {
await this.memoryCache.createTable(schema);
// v0.4.2-fix: schema 持久化(此前仅写空数据文件 → 空表重启后消失、索引标记丢失)
await this.setMeta(`schema_${schema.name}`, JSON.stringify(schema));
// OPFS 中表以空 JSON 数组文件形式存在
await this.writeTableData(schema.name, []);
});
}
async dropTable(tableName) {
await this.memoryCache.dropTable(tableName);
// v0.4.2-fix: 清理 schema meta(否则重启恢复幽灵表)
if (this.tablesDir) {
try {
await this.tablesDir.removeEntry(`__metona_schema_${tableName}.meta`);
return this.enqueueOp(async () => {
await this.memoryCache.dropTable(tableName);
// v0.4.2-fix: 清理 schema meta(否则重启恢复幽灵表)
if (this.tablesDir) {
try {
await this.tablesDir.removeEntry(`__metona_schema_${tableName}.meta`);
}
catch {
// 文件不存在则忽略
}
try {
await this.tablesDir.removeEntry(`${tableName}.json`);
}
catch {
// 文件不存在则忽略
}
}
catch {
// 文件不存在则忽略
}
try {
await this.tablesDir.removeEntry(`${tableName}.json`);
}
catch {
// 文件不存在则忽略
}
}
});
}
async hasTable(tableName) {
if (!this.tablesDir)
@@ -1717,20 +1755,24 @@ class OPFSEngine {
}
/** v0.4.2-fix: 引擎级 ALTER TABLE — 内存 + schema 持久化 + 整表文件重写 */
async alterTable(tableName, action, column) {
await this.memoryCache.alterTable(tableName, action, column);
const schema = await this.memoryCache.getTableSchema(tableName);
if (schema)
await this.setMeta(`schema_${tableName}`, JSON.stringify(schema));
const rows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, rows);
return this.enqueueOp(async () => {
await this.memoryCache.alterTable(tableName, action, column);
const schema = await this.memoryCache.getTableSchema(tableName);
if (schema)
await this.setMeta(`schema_${tableName}`, JSON.stringify(schema));
const rows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, rows);
});
}
// ---- CRUD ----
async insert(tableName, rows) {
const pks = await this.memoryCache.insert(tableName, rows);
// 持久化到 OPFS
const allRows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, allRows);
return pks;
return this.enqueueOp(async () => {
const pks = await this.memoryCache.insert(tableName, rows);
// 持久化到 OPFS(快照在队列内取,始终最新)
const allRows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, allRows);
return pks;
});
}
async find(tableName, query) {
return this.memoryCache.find(tableName, query);
@@ -1740,50 +1782,62 @@ class OPFSEngine {
return this.memoryCache.findStream(tableName, query, onRow);
}
async update(tableName, query, updates) {
const count = await this.memoryCache.update(tableName, query, updates);
const allRows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, allRows);
return count;
return this.enqueueOp(async () => {
const count = await this.memoryCache.update(tableName, query, updates);
const allRows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, allRows);
return count;
});
}
async delete(tableName, query) {
const count = await this.memoryCache.delete(tableName, query);
const allRows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, allRows);
return count;
return this.enqueueOp(async () => {
const count = await this.memoryCache.delete(tableName, query);
const allRows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, allRows);
return count;
});
}
async count(tableName, query) {
return this.memoryCache.count(tableName, query);
}
async clear(tableName) {
await this.memoryCache.clear(tableName);
await this.writeTableData(tableName, []);
return this.enqueueOp(async () => {
await this.memoryCache.clear(tableName);
await this.writeTableData(tableName, []);
});
}
// ---- 动态索引(v0.3.0 ----
async createIndex(tableName, column, unique) {
await this.memoryCache.createIndex(tableName, column, unique);
// v0.4.2-fix: 索引标记持久化(重启后索引结构恢复)
const schema = await this.memoryCache.getTableSchema(tableName);
if (schema)
await this.setMeta(`schema_${tableName}`, JSON.stringify(schema));
return this.enqueueOp(async () => {
await this.memoryCache.createIndex(tableName, column, unique);
// v0.4.2-fix: 索引标记持久化(重启后索引结构恢复)
const schema = await this.memoryCache.getTableSchema(tableName);
if (schema)
await this.setMeta(`schema_${tableName}`, JSON.stringify(schema));
});
}
async dropIndex(tableName, column, indexName) {
await this.memoryCache.dropIndex(tableName, column, indexName);
const schema = await this.memoryCache.getTableSchema(tableName);
if (schema)
await this.setMeta(`schema_${tableName}`, JSON.stringify(schema));
return this.enqueueOp(async () => {
await this.memoryCache.dropIndex(tableName, column, indexName);
const schema = await this.memoryCache.getTableSchema(tableName);
if (schema)
await this.setMeta(`schema_${tableName}`, JSON.stringify(schema));
});
}
// ---- 事务 ----
async beginTransaction() {
await this.memoryCache.beginTransaction();
}
async commitTransaction() {
await this.memoryCache.commitTransaction();
// 将内存数据刷到 OPFS
const tableNames = await this.memoryCache.getTableNames();
for (const tableName of tableNames) {
const rows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, rows);
}
return this.enqueueOp(async () => {
await this.memoryCache.commitTransaction();
// 将内存数据刷到 OPFS
const tableNames = await this.memoryCache.getTableNames();
for (const tableName of tableNames) {
const rows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, rows);
}
});
}
async rollbackTransaction() {
await this.memoryCache.rollbackTransaction();
@@ -1796,6 +1850,7 @@ class OPFSEngine {
return this.tablesDir;
}
async writeTableData(tableName, data) {
// 由 enqueueOp 串行化调用,此处直接写文件
const dir = this.ensureDir();
const fileName = `${tableName}.json`;
const fileHandle = await dir.getFileHandle(fileName, { create: true });
@@ -3270,6 +3325,11 @@ class LSM {
this.compacting = false; // 防止重复触发 compaction
/** 串行化 flush/compaction 链:保证持久化顺序与 id 分配顺序一致 */
this.flushChain = Promise.resolve();
/**
* v0.4.3-fix: 最近一次后台 flush/compaction 失败
* 后台失败不卡死链吞错防死锁但在显式 flush()/close() 时报告不静默
*/
this.lastBackgroundError = null;
this.memtableSizeThreshold = config.memtableSizeThreshold ?? DEFAULT_MEMTABLE_SIZE;
this.memtable = new MemTable(this.memtableSizeThreshold);
this.levelSizeMultiplier = config.levelSizeMultiplier ?? DEFAULT_LEVEL_SIZE_MULTIPLIER;
@@ -3367,11 +3427,26 @@ class LSM {
return this.flushChain
.then(task)
.catch((error) => {
// v0.4.3-fix: 记录失败(flush()/close() 时报告),不再完全静默吞错
this.lastBackgroundError = error;
// eslint-disable-next-line no-console
console.warn('[AriaEngine LSM] background flush/compaction failed:', error);
// catch 返回 undefined → 链恢复为 resolved,后续任务继续
});
}
/**
* v0.4.3-fix: 排空后台链 循环等待 flushChain 直到稳定
* 任务完成时可能级联调度新任务compaction 多级触发单次 await 等不到
* close/flush/clear 必须等待全部后台任务完成后才能安全关闭底层存储
*/
async drainChain() {
while (true) {
const chain = this.flushChain;
await chain;
if (this.flushChain === chain)
return;
}
}
/** 将指定 Immutable MemTable 刷盘为 SSTableid 由 store 按命名空间分配) */
async flushImmutableAsync(frozen) {
const entries = frozen.getAllEntries();
@@ -3411,27 +3486,27 @@ class LSM {
this.scheduleCompact(0);
}
}
/** 异步调度 compaction,使用 setTimeout 分片执行 */
/**
* 异步调度 compaction
* v0.4.3-fix: 去掉 setTimeout 分片 此前未触发的定时器在 close 后执行
* 用已关闭的 backend 写存储错误被吞 close reopen 时旧闭包引用新 backend 交叉污染
* 现在直接挂在 flushChain 串行执行close drainChain 能等到全部完成
*/
scheduleCompact(level) {
if (level >= MAX_LSM_LEVELS - 1 || this.compacting)
return;
this.compacting = true;
setTimeout(() => {
try {
this.flushChain = this.enqueueOnChain(() => this.compactLevelAsync(level));
this.flushChain = this.enqueueOnChain(() => this.compactLevelAsync(level).finally(() => {
this.compacting = false;
// 连续触发:如果 compaction 后仍然超标,继续调度
if (this.levels[level].length >= 4) {
this.scheduleCompact(level);
}
finally {
this.compacting = false;
// 连续触发:如果 compaction 后仍然超标,继续调度
if (this.levels[level].length >= 4) {
this.scheduleCompact(level);
}
// 检查下一级是否需要 compaction
if (level + 1 < MAX_LSM_LEVELS - 1 && this.levels[level + 1].length >= 4) {
this.scheduleCompact(level + 1);
}
// 检查下一级是否需要 compaction
if (level + 1 < MAX_LSM_LEVELS - 1 && this.levels[level + 1].length >= 4) {
this.scheduleCompact(level + 1);
}
}, 0);
}));
}
/** 背压场景下排队 compaction(写入路径调用) */
enqueueCompact(level) {
@@ -3447,6 +3522,8 @@ class LSM {
}
}).catch((error) => {
this.compacting = false;
// v0.4.3-fix: 记录失败(flush()/close() 时报告)
this.lastBackgroundError = error;
// eslint-disable-next-line no-console
console.warn('[AriaEngine LSM] background compaction failed:', error);
});
@@ -3457,10 +3534,11 @@ class LSM {
/**
* 预加载指定 key 范围内可能命中的所有 SSTable 到缓存
* 在同步扫描/查找之前调用保证 loadSSTableReader 不会因缓存未命中而返回 null
* 先等待 flush 链完成避免 flush 的缓存裁剪与预加载竞争驱逐刚加载的 SSTable
* v0.4.3-fix: 等待链稳定drainChain 后台 flush/compaction 在链上动态增长
* 单次 await compaction 仍可能合并 levels导致扫描时新 meta 缓存未命中而跳块丢数据
*/
async prefetchRange(startKey, endKey) {
await this.flushChain;
await this.drainChain();
this.trimCache();
const toLoad = [];
for (let level = 0; level < MAX_LSM_LEVELS; level++) {
@@ -3479,7 +3557,8 @@ class LSM {
async prefetchKeys(keys) {
if (keys.length === 0)
return;
await this.flushChain;
// v0.4.3-fix: 等待链稳定(同 prefetchRange,防 compaction 竞态丢数据)
await this.drainChain();
this.trimCache();
const toLoad = new Set();
for (let level = 0; level < MAX_LSM_LEVELS; level++) {
@@ -3670,8 +3749,14 @@ class LSM {
}
/** 等待所有排队的 flush/compaction 完成,并将剩余数据刷盘 */
async flush() {
// 等待链上已排队的 flush/compaction
await this.flushChain;
// v0.4.3-fix: 报告后台失败(消费一次,不永久吞错)
if (this.lastBackgroundError !== null) {
const error = this.lastBackgroundError;
this.lastBackgroundError = null;
throw new DatabaseError('AriaEngine background flush/compaction failed (data may be inconsistent)', 'ARIA_BACKGROUND_ERROR', error);
}
// v0.4.3-fix: 循环等待级联任务(flush 完成可能触发新的 compaction
await this.drainChain();
// 若仍有 frozen 数据未刷盘,在链尾追加
if (this.immutableMemtable) {
const frozen = this.immutableMemtable;
@@ -3684,8 +3769,12 @@ class LSM {
await this.flushImmutableAsync(frozen);
}
this.frozenMemtables = [];
// 刷盘完成后级联调度可能触发 compaction → 排空到稳定
await this.drainChain();
}
async clear() {
// v0.4.3-fix: 清空前排空后台任务(避免 compaction 在清空后写回残留 meta/数据)
await this.drainChain();
this.memtable.clear();
this.immutableMemtable = null;
this.frozenMemtables = [];
@@ -6300,6 +6389,16 @@ class AriaEngine {
async commitTransaction() {
if (!this.currentTxnId)
throw new DatabaseError('No active transaction', 'TX_NONE');
// v0.4.3-fix: 先持久化 WAL COMMIT,再合并快照到 LSM —
// 崩溃在 WAL 提交后、快照合并前:恢复时 WAL 重放数据,重启一致;
// 崩溃在 WAL 提交前:commitTransaction 尚未返回,事务视为未提交(可回滚)
await this.wal.append({
type: WALRecordType.COMMIT,
txnId: this.currentTxnId,
tableName: '',
key: '',
});
await this.wal.flush();
if (this.txnSnapshot) {
for (const [key, value] of this.txnSnapshot) {
if (value.__txn_deleted) {
@@ -6311,15 +6410,8 @@ class AriaEngine {
}
}
this.mvcc.commitTransaction(this.currentTxnId);
await this.wal.append({
type: WALRecordType.COMMIT,
txnId: this.currentTxnId,
tableName: '',
key: '',
});
this.currentTxnId = null;
this.txnSnapshot = null;
await this.wal.flush();
}
async rollbackTransaction() {
if (!this.currentTxnId)
+1 -1
View File
File diff suppressed because one or more lines are too long
+177 -85
View File
@@ -36,7 +36,7 @@
// ---------------------------------------------------------------------------
// 版本
// ---------------------------------------------------------------------------
const VERSION = '0.4.2';
const VERSION = '0.4.3';
/**
* metona-sqlark Shared WHERE Matcher 统一的条件匹配逻辑
@@ -1023,6 +1023,13 @@
}
}
async close() {
// v0.4.3-fix: 活跃事务时先回滚(避免 commit 对已关闭连接报错)
if (this.txActive) {
try {
await this.rollbackTransaction();
}
catch { /* 回滚失败不阻塞关闭 */ }
}
if (this.db) {
this.db.onversionchange = null; // 清理监听器
this.db.close();
@@ -1609,6 +1616,18 @@
this.dbName = '';
// 运行时内存缓存(OPFS 文件读写有延迟)
this.memoryCache = new MemoryEngine();
/**
* v0.4.3-fix: 写操作串行队列 内存写 + 快照 + 文件持久化整体排队执行
* close() 等待队列排空后再释放目录句柄避免 close 后挂起写泄漏/读旧数据
* 前一个操作失败不阻塞后续错误仍返回给调用方
*/
this.opQueue = Promise.resolve();
}
/** 将写操作加入串行队列(快照在队列内取,始终最新) */
enqueueOp(fn) {
const run = this.opQueue.then(fn, fn);
this.opQueue = run.then(() => undefined, () => undefined);
return run;
}
// ---- 生命周期 ----
async open(dbName, version) {
@@ -1622,6 +1641,11 @@
await this.loadExistingTables();
}
async close() {
// v0.4.3-fix: 等待所有挂起写操作完成(否则 close 后写仍在进行 → 重启读旧数据)
try {
await this.opQueue;
}
catch { /* 写失败已返回给调用方 */ }
this.root = null;
this.tablesDir = null;
await this.memoryCache.close();
@@ -1632,12 +1656,22 @@
// ---- v0.4.2-fix: 自愈 / 重置 / 元数据 ----
/** 自愈:重置内存缓存后从 OPFS 重新加载(单文件损坏不影响其他表) */
async repair() {
// v0.4.3-fix: 先等写队列排空(避免与挂起写竞态)
try {
await this.opQueue;
}
catch { /* ignore */ }
await this.memoryCache.close();
await this.memoryCache.open(this.dbName, 1);
await this.loadExistingTables();
}
/** 清空全部数据与表结构(删除目录内全部文件) */
async clearAll() {
// v0.4.3-fix: 先等写队列排空
try {
await this.opQueue;
}
catch { /* ignore */ }
await this.memoryCache.close();
await this.memoryCache.open(this.dbName, 1);
if (this.tablesDir) {
@@ -1672,29 +1706,33 @@
}
// ---- 表管理 ----
async createTable(schema) {
await this.memoryCache.createTable(schema);
// v0.4.2-fix: schema 持久化(此前仅写空数据文件 → 空表重启后消失、索引标记丢失)
await this.setMeta(`schema_${schema.name}`, JSON.stringify(schema));
// OPFS 中表以空 JSON 数组文件形式存在
await this.writeTableData(schema.name, []);
return this.enqueueOp(async () => {
await this.memoryCache.createTable(schema);
// v0.4.2-fix: schema 持久化(此前仅写空数据文件 → 空表重启后消失、索引标记丢失)
await this.setMeta(`schema_${schema.name}`, JSON.stringify(schema));
// OPFS 中表以空 JSON 数组文件形式存在
await this.writeTableData(schema.name, []);
});
}
async dropTable(tableName) {
await this.memoryCache.dropTable(tableName);
// v0.4.2-fix: 清理 schema meta(否则重启恢复幽灵表)
if (this.tablesDir) {
try {
await this.tablesDir.removeEntry(`__metona_schema_${tableName}.meta`);
return this.enqueueOp(async () => {
await this.memoryCache.dropTable(tableName);
// v0.4.2-fix: 清理 schema meta(否则重启恢复幽灵表)
if (this.tablesDir) {
try {
await this.tablesDir.removeEntry(`__metona_schema_${tableName}.meta`);
}
catch {
// 文件不存在则忽略
}
try {
await this.tablesDir.removeEntry(`${tableName}.json`);
}
catch {
// 文件不存在则忽略
}
}
catch {
// 文件不存在则忽略
}
try {
await this.tablesDir.removeEntry(`${tableName}.json`);
}
catch {
// 文件不存在则忽略
}
}
});
}
async hasTable(tableName) {
if (!this.tablesDir)
@@ -1723,20 +1761,24 @@
}
/** v0.4.2-fix: 引擎级 ALTER TABLE — 内存 + schema 持久化 + 整表文件重写 */
async alterTable(tableName, action, column) {
await this.memoryCache.alterTable(tableName, action, column);
const schema = await this.memoryCache.getTableSchema(tableName);
if (schema)
await this.setMeta(`schema_${tableName}`, JSON.stringify(schema));
const rows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, rows);
return this.enqueueOp(async () => {
await this.memoryCache.alterTable(tableName, action, column);
const schema = await this.memoryCache.getTableSchema(tableName);
if (schema)
await this.setMeta(`schema_${tableName}`, JSON.stringify(schema));
const rows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, rows);
});
}
// ---- CRUD ----
async insert(tableName, rows) {
const pks = await this.memoryCache.insert(tableName, rows);
// 持久化到 OPFS
const allRows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, allRows);
return pks;
return this.enqueueOp(async () => {
const pks = await this.memoryCache.insert(tableName, rows);
// 持久化到 OPFS(快照在队列内取,始终最新)
const allRows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, allRows);
return pks;
});
}
async find(tableName, query) {
return this.memoryCache.find(tableName, query);
@@ -1746,50 +1788,62 @@
return this.memoryCache.findStream(tableName, query, onRow);
}
async update(tableName, query, updates) {
const count = await this.memoryCache.update(tableName, query, updates);
const allRows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, allRows);
return count;
return this.enqueueOp(async () => {
const count = await this.memoryCache.update(tableName, query, updates);
const allRows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, allRows);
return count;
});
}
async delete(tableName, query) {
const count = await this.memoryCache.delete(tableName, query);
const allRows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, allRows);
return count;
return this.enqueueOp(async () => {
const count = await this.memoryCache.delete(tableName, query);
const allRows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, allRows);
return count;
});
}
async count(tableName, query) {
return this.memoryCache.count(tableName, query);
}
async clear(tableName) {
await this.memoryCache.clear(tableName);
await this.writeTableData(tableName, []);
return this.enqueueOp(async () => {
await this.memoryCache.clear(tableName);
await this.writeTableData(tableName, []);
});
}
// ---- 动态索引(v0.3.0 ----
async createIndex(tableName, column, unique) {
await this.memoryCache.createIndex(tableName, column, unique);
// v0.4.2-fix: 索引标记持久化(重启后索引结构恢复)
const schema = await this.memoryCache.getTableSchema(tableName);
if (schema)
await this.setMeta(`schema_${tableName}`, JSON.stringify(schema));
return this.enqueueOp(async () => {
await this.memoryCache.createIndex(tableName, column, unique);
// v0.4.2-fix: 索引标记持久化(重启后索引结构恢复)
const schema = await this.memoryCache.getTableSchema(tableName);
if (schema)
await this.setMeta(`schema_${tableName}`, JSON.stringify(schema));
});
}
async dropIndex(tableName, column, indexName) {
await this.memoryCache.dropIndex(tableName, column, indexName);
const schema = await this.memoryCache.getTableSchema(tableName);
if (schema)
await this.setMeta(`schema_${tableName}`, JSON.stringify(schema));
return this.enqueueOp(async () => {
await this.memoryCache.dropIndex(tableName, column, indexName);
const schema = await this.memoryCache.getTableSchema(tableName);
if (schema)
await this.setMeta(`schema_${tableName}`, JSON.stringify(schema));
});
}
// ---- 事务 ----
async beginTransaction() {
await this.memoryCache.beginTransaction();
}
async commitTransaction() {
await this.memoryCache.commitTransaction();
// 将内存数据刷到 OPFS
const tableNames = await this.memoryCache.getTableNames();
for (const tableName of tableNames) {
const rows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, rows);
}
return this.enqueueOp(async () => {
await this.memoryCache.commitTransaction();
// 将内存数据刷到 OPFS
const tableNames = await this.memoryCache.getTableNames();
for (const tableName of tableNames) {
const rows = await this.memoryCache.find(tableName, { table: tableName });
await this.writeTableData(tableName, rows);
}
});
}
async rollbackTransaction() {
await this.memoryCache.rollbackTransaction();
@@ -1802,6 +1856,7 @@
return this.tablesDir;
}
async writeTableData(tableName, data) {
// 由 enqueueOp 串行化调用,此处直接写文件
const dir = this.ensureDir();
const fileName = `${tableName}.json`;
const fileHandle = await dir.getFileHandle(fileName, { create: true });
@@ -3276,6 +3331,11 @@
this.compacting = false; // 防止重复触发 compaction
/** 串行化 flush/compaction 链:保证持久化顺序与 id 分配顺序一致 */
this.flushChain = Promise.resolve();
/**
* v0.4.3-fix: 最近一次后台 flush/compaction 失败
* 后台失败不卡死链吞错防死锁但在显式 flush()/close() 时报告不静默
*/
this.lastBackgroundError = null;
this.memtableSizeThreshold = config.memtableSizeThreshold ?? DEFAULT_MEMTABLE_SIZE;
this.memtable = new MemTable(this.memtableSizeThreshold);
this.levelSizeMultiplier = config.levelSizeMultiplier ?? DEFAULT_LEVEL_SIZE_MULTIPLIER;
@@ -3373,11 +3433,26 @@
return this.flushChain
.then(task)
.catch((error) => {
// v0.4.3-fix: 记录失败(flush()/close() 时报告),不再完全静默吞错
this.lastBackgroundError = error;
// eslint-disable-next-line no-console
console.warn('[AriaEngine LSM] background flush/compaction failed:', error);
// catch 返回 undefined → 链恢复为 resolved,后续任务继续
});
}
/**
* v0.4.3-fix: 排空后台链 循环等待 flushChain 直到稳定
* 任务完成时可能级联调度新任务compaction 多级触发单次 await 等不到
* close/flush/clear 必须等待全部后台任务完成后才能安全关闭底层存储
*/
async drainChain() {
while (true) {
const chain = this.flushChain;
await chain;
if (this.flushChain === chain)
return;
}
}
/** 将指定 Immutable MemTable 刷盘为 SSTableid 由 store 按命名空间分配) */
async flushImmutableAsync(frozen) {
const entries = frozen.getAllEntries();
@@ -3417,27 +3492,27 @@
this.scheduleCompact(0);
}
}
/** 异步调度 compaction,使用 setTimeout 分片执行 */
/**
* 异步调度 compaction
* v0.4.3-fix: 去掉 setTimeout 分片 此前未触发的定时器在 close 后执行
* 用已关闭的 backend 写存储错误被吞 close reopen 时旧闭包引用新 backend 交叉污染
* 现在直接挂在 flushChain 串行执行close drainChain 能等到全部完成
*/
scheduleCompact(level) {
if (level >= MAX_LSM_LEVELS - 1 || this.compacting)
return;
this.compacting = true;
setTimeout(() => {
try {
this.flushChain = this.enqueueOnChain(() => this.compactLevelAsync(level));
this.flushChain = this.enqueueOnChain(() => this.compactLevelAsync(level).finally(() => {
this.compacting = false;
// 连续触发:如果 compaction 后仍然超标,继续调度
if (this.levels[level].length >= 4) {
this.scheduleCompact(level);
}
finally {
this.compacting = false;
// 连续触发:如果 compaction 后仍然超标,继续调度
if (this.levels[level].length >= 4) {
this.scheduleCompact(level);
}
// 检查下一级是否需要 compaction
if (level + 1 < MAX_LSM_LEVELS - 1 && this.levels[level + 1].length >= 4) {
this.scheduleCompact(level + 1);
}
// 检查下一级是否需要 compaction
if (level + 1 < MAX_LSM_LEVELS - 1 && this.levels[level + 1].length >= 4) {
this.scheduleCompact(level + 1);
}
}, 0);
}));
}
/** 背压场景下排队 compaction(写入路径调用) */
enqueueCompact(level) {
@@ -3453,6 +3528,8 @@
}
}).catch((error) => {
this.compacting = false;
// v0.4.3-fix: 记录失败(flush()/close() 时报告)
this.lastBackgroundError = error;
// eslint-disable-next-line no-console
console.warn('[AriaEngine LSM] background compaction failed:', error);
});
@@ -3463,10 +3540,11 @@
/**
* 预加载指定 key 范围内可能命中的所有 SSTable 到缓存
* 在同步扫描/查找之前调用保证 loadSSTableReader 不会因缓存未命中而返回 null
* 先等待 flush 链完成避免 flush 的缓存裁剪与预加载竞争驱逐刚加载的 SSTable
* v0.4.3-fix: 等待链稳定drainChain 后台 flush/compaction 在链上动态增长
* 单次 await compaction 仍可能合并 levels导致扫描时新 meta 缓存未命中而跳块丢数据
*/
async prefetchRange(startKey, endKey) {
await this.flushChain;
await this.drainChain();
this.trimCache();
const toLoad = [];
for (let level = 0; level < MAX_LSM_LEVELS; level++) {
@@ -3485,7 +3563,8 @@
async prefetchKeys(keys) {
if (keys.length === 0)
return;
await this.flushChain;
// v0.4.3-fix: 等待链稳定(同 prefetchRange,防 compaction 竞态丢数据)
await this.drainChain();
this.trimCache();
const toLoad = new Set();
for (let level = 0; level < MAX_LSM_LEVELS; level++) {
@@ -3676,8 +3755,14 @@
}
/** 等待所有排队的 flush/compaction 完成,并将剩余数据刷盘 */
async flush() {
// 等待链上已排队的 flush/compaction
await this.flushChain;
// v0.4.3-fix: 报告后台失败(消费一次,不永久吞错)
if (this.lastBackgroundError !== null) {
const error = this.lastBackgroundError;
this.lastBackgroundError = null;
throw new DatabaseError('AriaEngine background flush/compaction failed (data may be inconsistent)', 'ARIA_BACKGROUND_ERROR', error);
}
// v0.4.3-fix: 循环等待级联任务(flush 完成可能触发新的 compaction
await this.drainChain();
// 若仍有 frozen 数据未刷盘,在链尾追加
if (this.immutableMemtable) {
const frozen = this.immutableMemtable;
@@ -3690,8 +3775,12 @@
await this.flushImmutableAsync(frozen);
}
this.frozenMemtables = [];
// 刷盘完成后级联调度可能触发 compaction → 排空到稳定
await this.drainChain();
}
async clear() {
// v0.4.3-fix: 清空前排空后台任务(避免 compaction 在清空后写回残留 meta/数据)
await this.drainChain();
this.memtable.clear();
this.immutableMemtable = null;
this.frozenMemtables = [];
@@ -6306,6 +6395,16 @@
async commitTransaction() {
if (!this.currentTxnId)
throw new DatabaseError('No active transaction', 'TX_NONE');
// v0.4.3-fix: 先持久化 WAL COMMIT,再合并快照到 LSM —
// 崩溃在 WAL 提交后、快照合并前:恢复时 WAL 重放数据,重启一致;
// 崩溃在 WAL 提交前:commitTransaction 尚未返回,事务视为未提交(可回滚)
await this.wal.append({
type: WALRecordType.COMMIT,
txnId: this.currentTxnId,
tableName: '',
key: '',
});
await this.wal.flush();
if (this.txnSnapshot) {
for (const [key, value] of this.txnSnapshot) {
if (value.__txn_deleted) {
@@ -6317,15 +6416,8 @@
}
}
this.mvcc.commitTransaction(this.currentTxnId);
await this.wal.append({
type: WALRecordType.COMMIT,
txnId: this.currentTxnId,
tableName: '',
key: '',
});
this.currentTxnId = null;
this.txnSnapshot = null;
await this.wal.flush();
}
async rollbackTransaction() {
if (!this.currentTxnId)
+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