Skip to content

perf(store): SQLite loop-safety, connection lifecycle & query pushdown - #16

Open
buggedcom wants to merge 1 commit into
mainfrom
perf/sqlite-store-loop-safety
Open

perf(store): SQLite loop-safety, connection lifecycle & query pushdown#16
buggedcom wants to merge 1 commit into
mainfrom
perf/sqlite-store-loop-safety

Conversation

@buggedcom

Copy link
Copy Markdown
Owner

Addresses all four findings from the SQLite event-store review.

1 — Reads no longer block the event loop

Previously the DB reads ran synchronously on the loop. Now:

  • Websocket: ws_get_events and ws_get_event_bounds run their reads via async_add_executor_job (these are the heavy ones — get_events can materialise a wide time range).
  • Sensors: added a _compute_blocks_io flag on the base. The 8 event-DB global sensors refresh off-loop through the executor; monitor sensors that read in-memory state keep refreshing inline (offloading those would race with loop-side mutations). One-time initial reads at construction stay inline.

2 — query() stops full-scanning

_EventDb.query() now pushes LIMIT/OFFSET into SQL when there's no entity filter, instead of loading the whole table and slicing in Python. With an entity filter the JSON-array intersection still runs in Python (indexed time-range in SQL first), then paginates.

3 & 4 — Connection lifecycle

_EventDb now holds a single long-lived, lock-guarded connection (check_same_thread=False + a threading.Lock serialising access) instead of opening a fresh connection per operation and relying on GC to close it. Added close() on _EventDb/DatapointsStore, called from async_unload_entry to release it on unload.

Tests

  • Added a sync-hass stub so offloaded sensor refreshes complete inline under test.
  • Updated websocket assertions to positional args (calls now go through async_add_executor_job).
  • All 425 Python tests pass; ruff check + ruff format clean.

Backend-only; frontend bundle changes are just the version banner. Bumps to 0.6.4.

🤖 Generated with Claude Code

… scans

Addresses the four findings from the store review:

1. Reads no longer block the event loop. The websocket handlers (ws_get_events,
   ws_get_event_bounds) and the DB-backed global sensors now run their blocking
   SQLite reads in the executor. Sensors gained a `_compute_blocks_io` flag: the
   eight event-DB sensors refresh via `async_add_executor_job`, while monitor
   sensors that read in-memory state keep refreshing inline (offloading those
   would race with loop-side mutations). One-time initial reads at construction
   are left inline.

2. _EventDb.query() pushes LIMIT/OFFSET into SQL when there is no entity filter,
   instead of materialising the whole table and paginating in Python. With an
   entity filter the JSON-array intersection still runs in Python (then paginates).

3 & 4. _EventDb holds a single long-lived, lock-guarded connection instead of
   opening — and leaking (relying on GC to close) — a fresh connection per
   operation. Added _EventDb.close()/DatapointsStore.close(), invoked from
   async_unload_entry so the connection is released on unload.

Tests: added a sync-hass stub so offloaded sensor refreshes run inline under
test; updated the websocket assertions to positional args. All 425 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant