mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-23 23:46:35 +00:00
feat(db): make the SQLite journal mode configurable (#1675)
WAL was hardcoded, and on network-backed storage — Azure App Service Linux, SMB/NFS volumes on NAS and PaaS — SQLite documents it as unsafe, because the coordination runs through the -shm file and mmap. Operators had no way out. TREK_DB_JOURNAL_MODE and TREK_DB_SYNCHRONOUS now set both pragmas, defaulting to today's behaviour. The mode lives in the file header, so all three processes that open travel.db read the same configuration — the server, reset-admin.js and migrate-encryption.ts, which used to hard-set WAL and would have silently reverted a deliberate DELETE setup on the first key rotation. An invalid value falls back and warns rather than refusing to start, and the mode that actually took effect is read back and logged, so an operator can see whether the setting landed.
This commit is contained in:
@@ -2,6 +2,7 @@ import Database from 'better-sqlite3';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { readEnv } from '../app-config';
|
||||
import { applyDurabilityPragmas } from './durability';
|
||||
import { createTables } from './schema';
|
||||
import { runMigrations } from './migrations';
|
||||
import { runSeeds } from './seeds';
|
||||
@@ -39,9 +40,16 @@ function initDb(): void {
|
||||
}
|
||||
|
||||
_db = new Database(dbPath);
|
||||
_db.exec('PRAGMA journal_mode = WAL');
|
||||
// Ahead of the journal switch now: changing journal_mode needs an exclusive
|
||||
// lock, which a sibling process (reset-admin, the rotation script) may hold.
|
||||
_db.exec('PRAGMA busy_timeout = 5000');
|
||||
const durability = applyDurabilityPragmas(_db);
|
||||
_db.exec('PRAGMA foreign_keys = ON');
|
||||
// Reported so an operator can see whether their setting took — the test DB is
|
||||
// :memory: and has no journal file, so there is nothing to report there.
|
||||
if (dbPath !== ':memory:') {
|
||||
console.log(`[DB] journal_mode=${durability.journalMode}, synchronous=${durability.synchronous}`);
|
||||
}
|
||||
|
||||
createTables(_db);
|
||||
runMigrations(_db);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import type Database from 'better-sqlite3';
|
||||
import { readEnv } from '../app-config';
|
||||
import { synchronousName } from '../app-config/parsers';
|
||||
|
||||
export interface ActiveDurability {
|
||||
journalMode: string;
|
||||
synchronous: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the configured journal_mode / synchronous pragmas to a freshly opened
|
||||
* connection and reports back what SQLite actually settled on.
|
||||
*
|
||||
* journal_mode is stored in the database file header, so it is not a per-process
|
||||
* setting: whichever process opened the file last decides the mode for all of
|
||||
* them. reset-admin.js and scripts/migrate-encryption.ts open the same file and
|
||||
* therefore read the same two variables — they cannot import this module (they
|
||||
* are standalone scripts and src/ is not in the image), so keep the three in
|
||||
* sync when the defaults change.
|
||||
*
|
||||
* The returned values are read back from the engine rather than echoed from the
|
||||
* config, so a mode SQLite refused shows up as what it really is — an in-memory
|
||||
* database stays MEMORY however hard you ask for WAL.
|
||||
*/
|
||||
export function applyDurabilityPragmas(db: Database.Database): ActiveDurability {
|
||||
const { journalMode, synchronous, durabilityWarnings } = readEnv().db;
|
||||
durabilityWarnings.forEach(warning => console.warn(`[DB] ${warning}`));
|
||||
|
||||
db.exec(`PRAGMA journal_mode = ${journalMode}`);
|
||||
db.exec(`PRAGMA synchronous = ${synchronous}`);
|
||||
|
||||
return {
|
||||
journalMode: String(db.pragma('journal_mode', { simple: true })).toUpperCase(),
|
||||
synchronous: synchronousName(db.pragma('synchronous', { simple: true })),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user