Files
meshcore-hub/tests/test_common/test_database.py
T
Louis King 38a57f4cd4 perf(api): run handlers in threadpool, tune SQLite, precompute is_observer
Three related performance fixes for slow (>500ms) API responses.

1. Stop blocking the event loop. Route handlers were declared `async def`
   but ran synchronous SQLAlchemy queries (and synchronous Redis calls via
   the cache decorator) directly on the event loop, serializing requests.
   Convert all handlers to sync `def` so FastAPI runs them in its
   threadpool, and make the `@cached` decorator dual-mode (sync wrapper for
   sync handlers, async wrapper preserved for a future async/Postgres path).

2. Tune SQLite for concurrency. Enable WAL, busy_timeout and
   synchronous=NORMAL on every connection, and size the pool above the
   threadpool so handlers don't wait on connections. In-memory SQLite is
   guarded (no overflow-pool kwargs).

3. Precompute an indexed `nodes.is_observer` flag. The `observer=true`
   filter scanned ~68k+ event rows to find a handful of observers (the
   advertisements page calls it with limit=500). Replace the 5-way OR of
   subqueries with `WHERE nodes.is_observer = ?`. The collector sets the
   flag on first observation (in add_event_observer); the cleanup job
   clears it once a node's events are all pruned; a migration adds the
   column/index and backfills from the existing union.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 23:35:39 +01:00

37 lines
1.4 KiB
Python

"""Tests for database engine configuration."""
from pathlib import Path
from sqlalchemy import text
from meshcore_hub.common.database import create_database_engine
class TestSqlitePragmas:
"""Verify concurrency-related SQLite pragmas are applied on connect."""
def test_wal_and_busy_timeout_enabled(self, tmp_path: Path) -> None:
"""File-based SQLite engines should run in WAL mode with a busy timeout."""
db_path = tmp_path / "pragma.db"
engine = create_database_engine(f"sqlite:///{db_path}")
try:
with engine.connect() as conn:
journal_mode = conn.execute(text("PRAGMA journal_mode")).scalar()
busy_timeout = conn.execute(text("PRAGMA busy_timeout")).scalar()
foreign_keys = conn.execute(text("PRAGMA foreign_keys")).scalar()
assert str(journal_mode).lower() == "wal"
assert busy_timeout is not None and int(busy_timeout) >= 5000
assert foreign_keys is not None and int(foreign_keys) == 1
finally:
engine.dispose()
def test_in_memory_engine_builds(self) -> None:
"""In-memory SQLite must still build (no overflow-pool kwargs)."""
engine = create_database_engine("sqlite:///:memory:")
try:
with engine.connect() as conn:
assert conn.execute(text("SELECT 1")).scalar() == 1
finally:
engine.dispose()