feat(storage): add Bitcask storage migration, binary WAL, and data compaction support
This commit is contained in:
93
ts/ts_migration/classes.storagemigrator.ts
Normal file
93
ts/ts_migration/classes.storagemigrator.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { migrateV0ToV1 } from './migrators/v0_to_v1.js';
|
||||
|
||||
/**
|
||||
* Detected storage format version.
|
||||
* - v0: Legacy JSON format ({db}/{coll}.json files)
|
||||
* - v1: Bitcask binary format ({db}/{coll}/data.rdb directories)
|
||||
*/
|
||||
type TStorageVersion = 0 | 1;
|
||||
|
||||
/**
|
||||
* StorageMigrator — runs before the Rust engine starts.
|
||||
*
|
||||
* Detects the current storage format version and runs the appropriate
|
||||
* migration chain. The Rust engine only knows the current format (v1).
|
||||
*
|
||||
* Migration is safe: original files are never modified or deleted.
|
||||
* On success, a console hint is printed about which old files can be removed.
|
||||
*/
|
||||
export class StorageMigrator {
|
||||
private storagePath: string;
|
||||
|
||||
constructor(storagePath: string) {
|
||||
this.storagePath = storagePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run any needed migrations. Safe to call even if storage is already current.
|
||||
*/
|
||||
async run(): Promise<void> {
|
||||
if (!fs.existsSync(this.storagePath)) {
|
||||
return; // No data yet — nothing to migrate
|
||||
}
|
||||
|
||||
const version = this.detectVersion();
|
||||
|
||||
if (version === 1) {
|
||||
return; // Already current
|
||||
}
|
||||
|
||||
if (version === 0) {
|
||||
console.log(`[smartdb] Detected v0 (JSON) storage format at ${this.storagePath}`);
|
||||
console.log(`[smartdb] Running migration v0 → v1 (Bitcask binary format)...`);
|
||||
|
||||
const deletableFiles = await migrateV0ToV1(this.storagePath);
|
||||
|
||||
if (deletableFiles.length > 0) {
|
||||
console.log(`[smartdb] Migration v0 → v1 complete.`);
|
||||
console.log(`[smartdb] The following old files can be safely deleted:`);
|
||||
for (const f of deletableFiles) {
|
||||
console.log(`[smartdb] ${f}`);
|
||||
}
|
||||
} else {
|
||||
console.log(`[smartdb] Migration v0 → v1 complete. No old files to clean up.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the storage format version by inspecting the directory structure.
|
||||
*
|
||||
* v0: {db}/{coll}.json files exist
|
||||
* v1: {db}/{coll}/data.rdb directories exist
|
||||
*/
|
||||
private detectVersion(): TStorageVersion {
|
||||
const entries = fs.readdirSync(this.storagePath, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
|
||||
const dbDir = path.join(this.storagePath, entry.name);
|
||||
const dbEntries = fs.readdirSync(dbDir, { withFileTypes: true });
|
||||
|
||||
for (const dbEntry of dbEntries) {
|
||||
// v1: subdirectory with data.rdb
|
||||
if (dbEntry.isDirectory()) {
|
||||
const dataRdb = path.join(dbDir, dbEntry.name, 'data.rdb');
|
||||
if (fs.existsSync(dataRdb)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
// v0: .json file (not .indexes.json)
|
||||
if (dbEntry.isFile() && dbEntry.name.endsWith('.json') && !dbEntry.name.endsWith('.indexes.json')) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Empty or unrecognized — treat as v1 (fresh start)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user