mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-06 12:27:39 +00:00
- SQLite: enable WAL mode and PRAGMA performance settings - ws.ts (public + private): fix clientConfigVersions memory leak - internal server: add rate limiting and request timeouts - audit log: fix flush re-queue feedback loop - memory: add monitoring instrumentation - security: remove debug log of full request body
93 lines
3.1 KiB
TypeScript
93 lines
3.1 KiB
TypeScript
import { drizzle as DrizzleSqlite } from "drizzle-orm/better-sqlite3";
|
|
import Database from "better-sqlite3";
|
|
import * as schema from "./schema/schema";
|
|
import path from "path";
|
|
import fs from "fs";
|
|
import { APP_PATH } from "@server/lib/consts";
|
|
import { existsSync, mkdirSync } from "fs";
|
|
|
|
export const location = path.join(APP_PATH, "db", "db.sqlite");
|
|
export const exists = checkFileExists(location);
|
|
|
|
bootstrapVolume();
|
|
|
|
function createDb() {
|
|
const sqlite = new Database(location);
|
|
|
|
// Enable WAL mode for dramatically better concurrent read/write
|
|
// performance. Without this, readers block writers and vice versa,
|
|
// causing severe contention when multiple subsystems (verifySession,
|
|
// TraefikConfigManager, audit log flushes, ping flushes) all share
|
|
// this single connection. WAL mode allows concurrent readers with a
|
|
// single writer, which is the typical access pattern.
|
|
sqlite.pragma("journal_mode = WAL");
|
|
|
|
// Wait up to 5 seconds when the database is locked instead of
|
|
// failing immediately with SQLITE_BUSY. This prevents transient
|
|
// write failures from causing audit log buffer re-queues and retry
|
|
// loops that accumulate memory.
|
|
sqlite.pragma("busy_timeout = 5000");
|
|
|
|
// NORMAL synchronous mode is safe with WAL and significantly reduces
|
|
// the time each write holds the database lock.
|
|
sqlite.pragma("synchronous = NORMAL");
|
|
|
|
// Increase the page cache to 64 MB (negative value = KB). The
|
|
// default (2 MB) causes frequent I/O round-trips on the large JOIN
|
|
// queries used by TraefikConfigManager, which block the event loop
|
|
// for longer than necessary.
|
|
sqlite.pragma("cache_size = -65536");
|
|
|
|
// Enable memory-mapped I/O for reads (256 MB). This allows the OS
|
|
// to serve read queries from the page cache without going through
|
|
// SQLite's own cache, reducing event-loop blocking time.
|
|
sqlite.pragma("mmap_size = 268435456");
|
|
|
|
return DrizzleSqlite(sqlite, {
|
|
schema
|
|
});
|
|
}
|
|
|
|
export const db = createDb();
|
|
export default db;
|
|
export const primaryDb = db;
|
|
export type Transaction = Parameters<
|
|
Parameters<(typeof db)["transaction"]>[0]
|
|
>[0];
|
|
export const DB_TYPE: "pg" | "sqlite" = "sqlite";
|
|
|
|
function checkFileExists(filePath: string): boolean {
|
|
try {
|
|
fs.accessSync(filePath);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function bootstrapVolume() {
|
|
const appPath = APP_PATH;
|
|
|
|
const dbDir = path.join(appPath, "db");
|
|
const logsDir = path.join(appPath, "logs");
|
|
|
|
// check if the db directory exists and create it if it doesn't
|
|
if (!existsSync(dbDir)) {
|
|
mkdirSync(dbDir, { recursive: true });
|
|
}
|
|
|
|
// check if the logs directory exists and create it if it doesn't
|
|
if (!existsSync(logsDir)) {
|
|
mkdirSync(logsDir, { recursive: true });
|
|
}
|
|
|
|
// THIS IS FOR TRAEFIK; NOT REALLY NEEDED, BUT JUST IN CASE
|
|
|
|
const traefikDir = path.join(appPath, "traefik");
|
|
|
|
// check if the traefik directory exists and create it if it doesn't
|
|
if (!existsSync(traefikDir)) {
|
|
mkdirSync(traefikDir, { recursive: true });
|
|
}
|
|
}
|