Files
iptvnator/apps/electron-backend/src/app/workers/database.worker-connection.ts
T
4grayandClaude Opus 4.7 e33cb70dec perf(database): add SQLite performance pragmas (synchronous, cache, mmap)
Pair the existing journal_mode=WAL with the companion pragmas it needs to
actually pay off, and reduce read latency on hot query paths.

- synchronous=NORMAL: ~2-10x faster writes when paired with WAL. Safe — only
  risks losing the last committed txn on power loss; DB stays consistent.
- cache_size=-64000: 64MB page cache (default is 2MB).
- temp_store=MEMORY: keep sort/group temp tables in RAM.
- mmap_size=268435456: 256MB memory-mapped I/O for reads.

Applied to both connection sites: the main read-write/read-only connection
in libs/shared/database and the EPG worker connection in electron-backend.
The per-connection pragmas (cache_size, temp_store, mmap_size) apply to the
read-only agent-backend connection too.

Inspired by matracey/iptvnator@4ad8f88; ported manually since the file has
diverged significantly and the worker connection didn't exist in that fork.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 3d6901072049
2026-05-01 21:02:52 +02:00

112 lines
3.1 KiB
TypeScript

import type BetterSqlite3 from 'better-sqlite3';
import * as schema from 'database-schema';
import { getIptvnatorDatabasePath } from 'database-path-utils';
import { workerData } from 'worker_threads';
import type { AppDatabase } from '../database/database.types';
import {
getNativeModuleSearchPaths,
getWorkerDataNativeModuleSearchPaths,
loadNativeModuleFromSearchPaths,
registerNativeModuleSearchPaths,
} from './worker-runtime-paths';
import {
compactSqlForTrace,
isSqlTraceEnabled,
trace,
} from '../services/debug-trace';
let Database: typeof BetterSqlite3;
let drizzleFactory:
| (typeof import('drizzle-orm/better-sqlite3'))['drizzle']
| undefined;
const nativeModuleSearchPaths = [
...getWorkerDataNativeModuleSearchPaths(workerData),
...getNativeModuleSearchPaths({
resourcesPath: (
process as NodeJS.Process & { resourcesPath?: string }
).resourcesPath,
}),
];
registerNativeModuleSearchPaths(nativeModuleSearchPaths);
function loadBetterSqlite3(): typeof BetterSqlite3 {
return loadNativeModuleFromSearchPaths({
moduleName: 'better-sqlite3',
loggerLabel: '[DB Worker]',
searchPaths: nativeModuleSearchPaths,
fallbackRequire: () =>
// eslint-disable-next-line @typescript-eslint/no-require-imports
require('better-sqlite3') as typeof BetterSqlite3,
});
}
function getDrizzleFactory(): (typeof import('drizzle-orm/better-sqlite3'))['drizzle'] {
if (drizzleFactory) {
return drizzleFactory;
}
// Require drizzle only after native lookup paths have been registered.
// Its better-sqlite3 driver resolves the native package at module load time.
// eslint-disable-next-line @typescript-eslint/no-require-imports
drizzleFactory = require('drizzle-orm/better-sqlite3').drizzle as (
typeof import('drizzle-orm/better-sqlite3')
)['drizzle'];
return drizzleFactory;
}
Database = loadBetterSqlite3();
let db: AppDatabase | null = null;
let sqlite: BetterSqlite3.Database | null = null;
export async function getWorkerDatabase(): Promise<AppDatabase> {
if (db) {
return db;
}
const filePath = getIptvnatorDatabasePath();
sqlite = new Database(filePath, {
verbose: isSqlTraceEnabled()
? (sql: string) => {
trace('sql-worker', 'query', {
sql: compactSqlForTrace(sql),
});
}
: undefined,
});
sqlite.pragma('foreign_keys = ON');
sqlite.pragma('journal_mode = WAL');
sqlite.pragma('busy_timeout = 5000');
sqlite.pragma('synchronous = NORMAL');
sqlite.pragma('cache_size = -64000');
sqlite.pragma('temp_store = MEMORY');
sqlite.pragma('mmap_size = 268435456');
if (isSqlTraceEnabled()) {
trace('sql-worker', 'open', {
filePath,
});
}
db = getDrizzleFactory()(sqlite, { schema });
return db;
}
export function closeWorkerDatabase(): void {
if (!sqlite) {
return;
}
sqlite.close();
if (isSqlTraceEnabled()) {
trace('sql-worker', 'close');
}
sqlite = null;
db = null;
}