perf(backend): cache global_settings reads in DatabaseService (#814)

getGlobalSettings() runs a SELECT * on every call and is hit from 22
files, including the auth middleware (every authenticated request),
the WebSocket upgrade handler (every connection), and the debug-mode
gate (every diagnostic log line). Cache the result inside the service
on first read and invalidate on updateGlobalSetting().

The cached snapshot is Object.freeze'd and the public return type is
now Readonly<Record<string, string>> so accidental mutations are
caught at compile time. The settings GET handler that delete'd private
keys now takes a defensive shallow copy first.

The 5-second TTL cache in utils/debug.ts is now redundant and removed;
the service-level cache is strictly fresher (invalidates on write
rather than going stale for up to 5s).
This commit is contained in:
Anso
2026-04-27 23:45:25 -04:00
committed by GitHub
parent 502ee83438
commit 836e384d17
3 changed files with 25 additions and 23 deletions
+5 -17
View File
@@ -1,30 +1,18 @@
/**
* Shared diagnostic logging gate.
*
* Reads `developer_mode` from the global settings, cached for a short
* window so hot paths (per-request, per-log-line) do not hit SQLite
* on every call.
* Shared diagnostic logging gate. Reads `developer_mode` from
* DatabaseService, which caches the global_settings snapshot internally
* and invalidates on write, so hot-path callers can query freely.
*/
let cachedValue = false;
let cacheExpiry = 0;
const CACHE_TTL_MS = 5_000;
export function isDebugEnabled(): boolean {
const now = Date.now();
if (now < cacheExpiry) return cachedValue;
try {
// Dynamic require avoids circular-dependency issues when this
// utility is imported from services that DatabaseService itself
// depends on, and prevents SQLite side effects during tests.
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { DatabaseService } = require('../services/DatabaseService');
cachedValue = DatabaseService.getInstance().getGlobalSettings().developer_mode === '1';
return DatabaseService.getInstance().getGlobalSettings().developer_mode === '1';
} catch {
cachedValue = false;
return false;
}
cacheExpiry = now + CACHE_TTL_MS;
return cachedValue;
}