mirror of
https://github.com/ipnet-mesh/meshcore-hub.git
synced 2026-06-22 19:15:07 +02:00
38a57f4cd4
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>
37 lines
1.4 KiB
Python
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()
|