feat: v0.2.4 惰性扫描 + 写背压 + 内存预算 + ANALYZE + 加密 + Savepoint + 在线备份
This commit is contained in:
@@ -302,6 +302,7 @@ export class AriaEngine implements IStorageEngine {
|
||||
}
|
||||
|
||||
this.opCounter += rows.length;
|
||||
this.checkMemoryBudget();
|
||||
await this.checkpointManager.tick();
|
||||
this.tryGC();
|
||||
return pks;
|
||||
@@ -519,6 +520,49 @@ export class AriaEngine implements IStorageEngine {
|
||||
this.currentTxnId = null;
|
||||
}
|
||||
|
||||
// ---- Savepoint 嵌套事务 ----
|
||||
|
||||
private savepoints: Map<string, { txnId: number; snapshot: Map<string, Record<string, unknown>> | null }> = new Map();
|
||||
|
||||
async savepoint(name: string): Promise<void> {
|
||||
if (!this.currentTxnId) throw new DatabaseError('No active transaction for savepoint', 'TX_NONE');
|
||||
if (this.savepoints.has(name)) throw new DatabaseError(`Savepoint "${name}" already exists`, 'SAVEPOINT_EXISTS');
|
||||
// 保存当前事务快照
|
||||
this.savepoints.set(name, {
|
||||
txnId: this.currentTxnId,
|
||||
snapshot: this.txnSnapshot ? new Map(this.txnSnapshot) : null,
|
||||
});
|
||||
}
|
||||
|
||||
async rollbackToSavepoint(name: string): Promise<void> {
|
||||
const sp = this.savepoints.get(name);
|
||||
if (!sp) throw new DatabaseError(`Savepoint "${name}" not found`, 'SAVEPOINT_NOT_FOUND');
|
||||
// 恢复到 savepoint 时的快照
|
||||
this.txnSnapshot = sp.snapshot ? new Map(sp.snapshot) : null;
|
||||
// 清除此 savepoint 之后的所有 savepoint
|
||||
let found = false;
|
||||
for (const [k] of this.savepoints) {
|
||||
if (k === name) { found = true; continue; }
|
||||
if (found) this.savepoints.delete(k);
|
||||
}
|
||||
}
|
||||
|
||||
async releaseSavepoint(name: string): Promise<void> {
|
||||
if (!this.savepoints.has(name)) throw new DatabaseError(`Savepoint "${name}" not found`, 'SAVEPOINT_NOT_FOUND');
|
||||
this.savepoints.delete(name);
|
||||
}
|
||||
|
||||
// ---- 在线备份 ----
|
||||
|
||||
async backup(): Promise<Record<string, Record<string, unknown>[]>> {
|
||||
this.ensureOpen();
|
||||
const result: Record<string, Record<string, unknown>[]> = {};
|
||||
for (const tableName of this.schemas.keys()) {
|
||||
result[tableName] = this.getAllRows(tableName);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// =======================================================================
|
||||
// 内部
|
||||
// =======================================================================
|
||||
@@ -821,6 +865,50 @@ export class AriaEngine implements IStorageEngine {
|
||||
}
|
||||
}
|
||||
|
||||
/** 检查内存预算,超出时强制 flush + GC */
|
||||
private checkMemoryBudget(): void {
|
||||
const maxBytes = this.config.maxMemoryMB * 1024 * 1024;
|
||||
const used = this.lsm.getEstimatedMemory();
|
||||
if (used > maxBytes) {
|
||||
this.lsm.flush().catch(() => {});
|
||||
this.mvcc.gc(50);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ANALYZE: 收集表统计信息
|
||||
* 返回行数、平均行大小、索引深度等
|
||||
*/
|
||||
async analyzeTable(tableName: string): Promise<Record<string, unknown>> {
|
||||
this.ensureOpen();
|
||||
this.ensureTable(tableName);
|
||||
const rows = this.getAllRows(tableName);
|
||||
const stats: Record<string, unknown> = {
|
||||
table: tableName,
|
||||
rowCount: rows.length,
|
||||
avgRowSize: rows.length > 0
|
||||
? Math.round(rows.reduce((s, r) => s + JSON.stringify(r).length, 0) / rows.length)
|
||||
: 0,
|
||||
indexDepth: this.lsm.getStats().levelCounts.filter((c: number) => c > 0).length,
|
||||
sstableCount: this.lsm.getStats().sstableCount,
|
||||
memtableSize: this.lsm.getStats().memtableSize,
|
||||
estimatedMemory: this.lsm.getEstimatedMemory(),
|
||||
};
|
||||
|
||||
// 列基数统计
|
||||
const schema = this.schemas.get(tableName);
|
||||
if (schema && rows.length > 0) {
|
||||
const columnStats: Record<string, unknown> = {};
|
||||
for (const colName of Object.keys(schema.columns)) {
|
||||
const values = new Set(rows.map((r) => String(r[colName])));
|
||||
columnStats[colName] = { distinctValues: values.size };
|
||||
}
|
||||
stats.columnStats = columnStats;
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
private ensureOpen(): void {
|
||||
if (!this.opened) throw new DatabaseError('AriaEngine not opened', 'DB_NOT_OPEN');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user