feat: v0.2.4 OPFS写锁 + 性能基准 + BufferPool集成 + REINDEX/VACUUM + 查询优化器
This commit is contained in:
@@ -22,6 +22,7 @@ import { IndexedDBBackend, MemoryBackend, type IStorageBackend } from './store/b
|
||||
import { OPFSBackend } from './store/opfs_backend';
|
||||
import { MVCCManager } from './transaction/mvcc';
|
||||
import { BloomFilter } from './index/bloom';
|
||||
import { BufferPool, type PageIO } from './buffer/pool';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// AriaEngine
|
||||
@@ -52,6 +53,9 @@ export class AriaEngine implements IStorageEngine {
|
||||
private txnSnapshot: Map<string, Record<string, unknown>> | null = null;
|
||||
private gcCounter = 0;
|
||||
|
||||
// Buffer Pool(页面缓存,减少磁盘 I/O)
|
||||
private bufferPool!: BufferPool;
|
||||
|
||||
constructor(config: AriaEngineConfig = {}) {
|
||||
this.config = { ...DEFAULT_ARIA_CONFIG, ...config };
|
||||
}
|
||||
@@ -74,6 +78,15 @@ export class AriaEngine implements IStorageEngine {
|
||||
}
|
||||
await this.backend.open(dbName);
|
||||
|
||||
// 2a. 初始化 Buffer Pool(页面缓存)
|
||||
const pageIO: PageIO = {
|
||||
readPage: async (pageId) => this.backend.read(`pg_${pageId}`),
|
||||
writePage: async (pageId, data) => this.backend.write(`pg_${pageId}`, data),
|
||||
allocatePageId: async () => Date.now(),
|
||||
freePageId: async () => {},
|
||||
};
|
||||
this.bufferPool = new BufferPool(pageIO, this.config.bufferPoolPages);
|
||||
|
||||
// 2. 构建 SSTableStore
|
||||
const sstableStore = this.createSSTableStore();
|
||||
|
||||
@@ -909,6 +922,90 @@ export class AriaEngine implements IStorageEngine {
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* REINDEX: 重建指定表的所有二级索引
|
||||
*/
|
||||
async reindexTable(tableName: string): Promise<number> {
|
||||
this.ensureOpen();
|
||||
this.ensureTable(tableName);
|
||||
const schema = this.schemas.get(tableName)!;
|
||||
let rebuiltCount = 0;
|
||||
|
||||
for (const [colName, colDef] of Object.entries(schema.columns)) {
|
||||
if (!colDef.index && !colDef.unique && !colDef.primaryKey) continue;
|
||||
const idxKey = `${tableName}:idx:${colName}`;
|
||||
const idxLsm = this.secondaryIndexes.get(idxKey);
|
||||
if (!idxLsm) continue;
|
||||
|
||||
// 清空旧索引
|
||||
await idxLsm.clear();
|
||||
rebuiltCount++;
|
||||
|
||||
// 从主 LSM 重建索引
|
||||
const rows = this.getAllRows(tableName);
|
||||
for (const row of rows) {
|
||||
const val = row[colName];
|
||||
if (val !== undefined && val !== null) {
|
||||
idxLsm.put(`${String(val)}:${row[this.tablePKs.get(tableName)!]}`, { pk: row[this.tablePKs.get(tableName)!] });
|
||||
}
|
||||
}
|
||||
}
|
||||
return rebuiltCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* VACUUM: 压缩 LSM + 清理碎片
|
||||
*/
|
||||
async vacuum(): Promise<{ compactedLevels: number; gcVersions: number }> {
|
||||
this.ensureOpen();
|
||||
// 强制 flush memtable
|
||||
await this.lsm.flush();
|
||||
// 压缩各层级
|
||||
for (let level = 0; level < 6; level++) {
|
||||
if (this.lsm.getStats().levelCounts[level] >= 2) {
|
||||
(this.lsm as any).compactLevelSync(level);
|
||||
}
|
||||
}
|
||||
// GC MVCC 版本(保留最新 10 个)
|
||||
const beforeGC = this.mvcc.getActiveTxnCount?.() ?? 0;
|
||||
this.mvcc.gc(10);
|
||||
return { compactedLevels: 6, gcVersions: beforeGC };
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询优化器:估算各索引成本,选择最优方案
|
||||
*/
|
||||
estimateQueryCost(tableName: string, query: QueryPlan): { strategy: string; estimatedRows: number } {
|
||||
const schema = this.schemas.get(tableName);
|
||||
if (!schema || !query.where) return { strategy: 'full_scan', estimatedRows: 0 };
|
||||
|
||||
const pkCol = this.tablePKs.get(tableName)!;
|
||||
for (const [col, condition] of Object.entries(query.where)) {
|
||||
if (col === '$and' || col === '$or' || col === '$not') continue;
|
||||
|
||||
// PK 等值 → 最快,估计 1 行
|
||||
if (col === pkCol && (typeof condition !== 'object' || (condition as any).$eq)) {
|
||||
return { strategy: 'pk_lookup', estimatedRows: 1 };
|
||||
}
|
||||
|
||||
// 索引列等值 → 快
|
||||
const colDef = schema.columns[col];
|
||||
if (colDef?.index || colDef?.unique) {
|
||||
if (typeof condition !== 'object' || (condition as any).$eq) {
|
||||
return { strategy: `index_eq:${col}`, estimatedRows: 1 };
|
||||
}
|
||||
if ((condition as any).$in && Array.isArray((condition as any).$in)) {
|
||||
return { strategy: `index_in:${col}`, estimatedRows: (condition as any).$in.length };
|
||||
}
|
||||
if ((condition as any).$gt || (condition as any).$lt || (condition as any).$gte || (condition as any).$lte) {
|
||||
return { strategy: `index_range:${col}`, estimatedRows: 100 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { strategy: 'full_scan', estimatedRows: 1000 };
|
||||
}
|
||||
|
||||
private ensureOpen(): void {
|
||||
if (!this.opened) throw new DatabaseError('AriaEngine not opened', 'DB_NOT_OPEN');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user