diff --git a/packages/storage/src/__tests__/operational-state-store.test.ts b/packages/storage/src/__tests__/operational-state-store.test.ts index f2b5129fbd..651120ec43 100644 --- a/packages/storage/src/__tests__/operational-state-store.test.ts +++ b/packages/storage/src/__tests__/operational-state-store.test.ts @@ -69,6 +69,23 @@ test('shares one operational database and produces an online backup', async () = } }); +test('new runtime databases use incremental vacuum before operational schema creation', async () => { + const root = await mkdtemp(join(tmpdir(), 'maka-operational-incremental-vacuum-')); + try { + const lease = acquireOperationalStateDatabase(root); + try { + assert.equal( + (lease.database.prepare('PRAGMA auto_vacuum').get() as { auto_vacuum: number }).auto_vacuum, + 2, + ); + } finally { + lease.close(); + } + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + test('atomically reapplies current owner schema without republishing its registry', async () => { const root = await mkdtemp(join(tmpdir(), 'maka-operational-current-convergence-')); const databasePath = join(root, 'runtime.sqlite'); diff --git a/packages/storage/src/__tests__/sqlite-runtime-store.test.ts b/packages/storage/src/__tests__/sqlite-runtime-store.test.ts index ab9aa0f4f4..d490e77b3c 100644 --- a/packages/storage/src/__tests__/sqlite-runtime-store.test.ts +++ b/packages/storage/src/__tests__/sqlite-runtime-store.test.ts @@ -64,6 +64,48 @@ const PREFIX_PROOF_TEST_BUDGET = { }; describe('SqliteRuntimeStore', () => { + it('leaves an existing NONE auto-vacuum database unchanged', async () => { + const root = await mkdtemp(join(tmpdir(), 'maka-sqlite-runtime-existing-none-')); + const dbPath = join(root, 'runtime.sqlite'); + const existing = new DatabaseSync(dbPath); + existing.exec('CREATE TABLE existing_data (value TEXT NOT NULL)'); + existing.close(); + try { + const store = createSqliteRuntimeStore(dbPath); + store.close(); + const inspected = new DatabaseSync(dbPath, { readOnly: true }); + try { + assert.equal( + (inspected.prepare('PRAGMA auto_vacuum').get() as { auto_vacuum: number }).auto_vacuum, + 0, + ); + assert.ok( + inspected + .prepare("SELECT 1 FROM sqlite_schema WHERE type = 'table' AND name = 'existing_data'") + .get(), + ); + } finally { + inspected.close(); + } + } finally { + await rm(root, { recursive: true, force: true }); + } + }); + + it('creates runtime databases with incremental auto-vacuum', async () => { + await withStore(async (_store, dbPath) => { + const inspected = new DatabaseSync(dbPath, { readOnly: true }); + try { + assert.equal( + (inspected.prepare('PRAGMA auto_vacuum').get() as { auto_vacuum: number }).auto_vacuum, + 2, + ); + } finally { + inspected.close(); + } + }); + }); + it('reads a complete Session from one snapshot while another connection commits its terminal', async (t) => { await withStore(async (store, dbPath) => { const opening = invocationOpeningEvent(1); diff --git a/packages/storage/src/operational-state-store.ts b/packages/storage/src/operational-state-store.ts index eb80fe0280..a111215528 100644 --- a/packages/storage/src/operational-state-store.ts +++ b/packages/storage/src/operational-state-store.ts @@ -23,6 +23,7 @@ import { createRequire } from 'node:module'; import type { DatabaseSync } from 'node:sqlite'; import { configureSqliteRuntimeDatabase, + configureSqliteRuntimeAutoVacuumForNewDatabase, configureSqliteRuntimeLockWait, migrateSqliteRuntimeDatabase, readUserVersion, @@ -208,7 +209,8 @@ class OperationalStateDatabaseOwner { readonly databasePath: string, options: OperationalStateDatabaseOptions, ) { - if (options.schemaMigration === 'require_current' && !existsSync(databasePath)) { + const databaseExisted = existsSync(databasePath); + if (options.schemaMigration === 'require_current' && !databaseExisted) { throw new OperationalStateMigrationBlockedError( new Error('Operational state has not been initialized by its Runtime Host'), 'requires_host_migration', @@ -223,6 +225,9 @@ class OperationalStateDatabaseOwner { try { configureSqliteRuntimeLockWait(this.database); this.database.exec('PRAGMA foreign_keys = ON'); + if (options.schemaMigration !== 'require_current') { + configureSqliteRuntimeAutoVacuumForNewDatabase(this.database, databaseExisted); + } if (options.schemaMigration === 'require_current') { requireCurrentOperationalState(this.database); } else { diff --git a/packages/storage/src/sqlite-runtime-schema.ts b/packages/storage/src/sqlite-runtime-schema.ts index e1e8d5b181..5591162eec 100644 --- a/packages/storage/src/sqlite-runtime-schema.ts +++ b/packages/storage/src/sqlite-runtime-schema.ts @@ -954,6 +954,25 @@ export function configureSqliteRuntimeDatabase(db: DatabaseSync): void { db.exec('PRAGMA foreign_keys = ON'); } +/** Set the reclamation mode before schema creation on a newly created file only. */ +export function configureSqliteRuntimeAutoVacuumForNewDatabase( + db: DatabaseSync, + databaseExisted: boolean, +): void { + if (databaseExisted) return; + const existingSchema = db + .prepare("SELECT 1 AS present FROM sqlite_schema WHERE name NOT LIKE 'sqlite_%' LIMIT 1") + .get() as { present?: unknown } | undefined; + if (existingSchema?.present === 1) return; + db.exec('PRAGMA auto_vacuum = INCREMENTAL'); + const configured = db.prepare('PRAGMA auto_vacuum').get() as + | { auto_vacuum?: unknown } + | undefined; + if (configured?.auto_vacuum !== 2) { + throw new Error('New SQLite runtime database did not enable incremental auto-vacuum'); + } +} + /** Configure connection-local lock waiting without changing persistent database state. */ export function configureSqliteRuntimeLockWait(db: DatabaseSync): void { db.exec(`PRAGMA busy_timeout = ${SQLITE_INITIALIZATION_BUSY_TIMEOUT_MS}`); diff --git a/packages/storage/src/sqlite-runtime-store.ts b/packages/storage/src/sqlite-runtime-store.ts index 25bcb45421..06b0d973ab 100644 --- a/packages/storage/src/sqlite-runtime-store.ts +++ b/packages/storage/src/sqlite-runtime-store.ts @@ -36,7 +36,7 @@ export type { ImmutableRuntimePrefixProofReadBudget, } from './runtime-event-store-contract.js'; -import { mkdirSync } from 'node:fs'; +import { existsSync, mkdirSync } from 'node:fs'; import { createRequire } from 'node:module'; import { dirname } from 'node:path'; import type { DatabaseSync, SQLInputValue } from 'node:sqlite'; @@ -133,6 +133,7 @@ import { } from '@maka/core/tool-recovery-bundle'; import { configureSqliteRuntimeDatabase, + configureSqliteRuntimeAutoVacuumForNewDatabase, migrateSqliteRuntimeDatabase, readUserVersion, RUNTIME_RECOVERY_AUTHORITY_CAPABILITY, @@ -345,6 +346,7 @@ export class SqliteRuntimeStore return; } const DatabaseSync = loadDatabaseSync(); + const databaseExisted = path !== ':memory:' && existsSync(path); this.db = options.readOnly ? new DatabaseSync(path, { readOnly: true }) : new DatabaseSync(path); @@ -358,6 +360,7 @@ export class SqliteRuntimeStore ); } } else { + configureSqliteRuntimeAutoVacuumForNewDatabase(this.db, databaseExisted); configureSqliteRuntimeDatabase(this.db); migrateSqliteRuntimeDatabase(this.db); }