fix: kein WAL mehr, damit die read-only Web-App die DB öffnen kann

Eine readonly-Verbindung (web-Container) kann eine WAL-Datenbank ohne aktiven
Writer/-shm nicht öffnen -> "attempt to write a readonly database", die Seite
blieb leer. Da collector (Writer) und web (Reader) getrennte Prozesse sind und
der collector keine Dauerverbindung hält, existiert im Ruhezustand kein -shm.

- collector/db.py + schema.sql: WAL entfernt (Rollback-Journal als Default);
  readonly-Reads sind damit jederzeit möglich.
- web/lib/db.ts: busy_timeout=5s, falls gerade geschrieben wird.

Bestehende DB einmalig umstellen: PRAGMA journal_mode=DELETE;

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-05 15:28:08 +02:00
parent ca708b5dda
commit 5f695ebe2a
3 changed files with 7 additions and 4 deletions
+4 -1
View File
@@ -17,7 +17,10 @@ def connect() -> sqlite3.Connection:
Path(path).parent.mkdir(parents=True, exist_ok=True) Path(path).parent.mkdir(parents=True, exist_ok=True)
conn = sqlite3.connect(path) conn = sqlite3.connect(path)
conn.row_factory = sqlite3.Row conn.row_factory = sqlite3.Row
conn.execute("PRAGMA journal_mode=WAL;") # Bewusst KEIN WAL: Der web-Container liest die DB read-only aus einem
# anderen Prozess. Eine readonly-Verbindung kann eine WAL-Datenbank ohne
# aktiven Writer/-shm nicht öffnen ("attempt to write a readonly database").
# Rollback-Journal (Default) lässt readonly-Reads jederzeit zu.
return conn return conn
-2
View File
@@ -1,5 +1,3 @@
PRAGMA journal_mode=WAL;
CREATE TABLE IF NOT EXISTS charges ( CREATE TABLE IF NOT EXISTS charges (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
start_time TEXT NOT NULL, -- ISO 8601, lokale Zeit (Europe/Berlin) start_time TEXT NOT NULL, -- ISO 8601, lokale Zeit (Europe/Berlin)
+3 -1
View File
@@ -32,7 +32,9 @@ export interface Totals {
function withDb<T>(fallback: T, fn: (conn: Database.Database) => T): T { function withDb<T>(fallback: T, fn: (conn: Database.Database) => T): T {
let conn: Database.Database | null = null; let conn: Database.Database | null = null;
try { try {
conn = new Database(DB_PATH, { readonly: true, fileMustExist: true }); // timeout: wartet kurz, falls der collector gerade schreibt (statt
// sofort mit SQLITE_BUSY zu scheitern).
conn = new Database(DB_PATH, { readonly: true, fileMustExist: true, timeout: 5000 });
const hasTable = conn const hasTable = conn
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='charges'") .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='charges'")
.get(); .get();