diff --git a/src/psyc/db.py b/src/psyc/db.py index a6e66b0..d358882 100644 --- a/src/psyc/db.py +++ b/src/psyc/db.py @@ -17,11 +17,13 @@ from sqlalchemy import ( Table, Text, create_engine, + event, func, insert, select, ) from sqlalchemy.dialects.sqlite import insert as sqlite_insert +from sqlalchemy.pool import NullPool from psyc import DATA_DIR, log from psyc.models import Case @@ -209,10 +211,31 @@ _engine: Optional[Engine] = None def engine(db_path: Path = DB_PATH) -> Engine: + """Lazy-init the SQLite engine. + + Uses NullPool — SQLite doesn't benefit from connection pooling (it's a + file, opens are cheap) and the default QueuePool starved the classify + + federation + cockpit-request workers under real load. WAL journal mode + + a 30s busy timeout let readers and a writer share the file safely. + """ global _engine if _engine is None: db_path.parent.mkdir(parents=True, exist_ok=True) - _engine = create_engine(f"sqlite:///{db_path}", future=True) + _engine = create_engine( + f"sqlite:///{db_path}", + future=True, + poolclass=NullPool, + connect_args={"check_same_thread": False, "timeout": 30}, + ) + + @event.listens_for(_engine, "connect") + def _sqlite_pragmas(dbapi_conn, _connection_record): # noqa: D401 + cur = dbapi_conn.cursor() + cur.execute("PRAGMA journal_mode=WAL") + cur.execute("PRAGMA synchronous=NORMAL") + cur.execute("PRAGMA busy_timeout=30000") + cur.close() + return _engine