perf(state): read-path split — per-thread read-only connections for recall reads - #73344
Conversation
91a359d to
454496c
Compare
|
found two issues that need to be addressed before merge. both are reproducible on WAL with SQLite 3.53.1.
|
454496c to
6d72dcb
Compare
|
Both issues fixed in the latest push. 1. 2. Short-lived reader threads no longer leak tracked connections. Read connections are now registered in a |
|
Reverified PR head
|
…ecall reads The gateway shares ONE SessionDB across every agent, so every recall/browse read (session_search discover/scroll/browse, memory prefetch, title resolve) queued behind every writer flush on self._lock — one Python lock in front of a WAL database that natively supports concurrent readers. Measured convoy: a 0.23s FTS query stretched to 112s and a browse flush to 137s while 6-8 concurrent turns flushed hundreds of tool results. Fix: under WAL, read-only methods (get_session, resolve_session_by_title, list_sessions_rich, get_messages, get_messages_around, get_anchored_view, search_messages) run on a per-thread mode=ro connection via _read_ctx(), taking no lock at all. Fresh read transactions begin per statement, so read-your-committed-writes holds for flush-then-search patterns. Non-WAL (NFS DELETE fallback) or read-conn open failure keeps the legacy locked single-connection path, remembered per thread to avoid per-query retries.
6d72dcb to
194447d
Compare
|
All three issues addressed in the latest push (rebased onto current 1. Already fixed — 2. Fixed — switched from WeakSet to a strong set. The WeakSet lost references when reader threads exited, so 3. Rebased cleanly onto current
472 passed, 6 skipped (5 WAL-gated, 1 pre-existing). |
|
One last lifecycle issue, sorry, I missed this on the first pass. |
…remaining read paths, mark WAL tests - Route _get_read_conn through _connect_tracked_db so per-thread read-only connections are registered with the POSIX lock-safety guard (connect_tracked), matching the writer and existing read-only paths. Without this, byte-level probes of state.db could close() an fd that cancels locks held by an untracked read connection. - Convert _search_unindexed_gap, _run_trigram_search, CJK-bigram FTS search, and get_meta to _read_ctx — these are pure SELECT queries called from search_messages that were still taking self._lock, defeating the PR's contention fix for those paths. - Add @pytest.mark.requires_wal to the 5 tests that assume WAL is active. Hermes disables WAL on SQLite < 3.51.3 (WAL-reset bug), so these tests fail on the venv's SQLite 3.46.0 without the marker. - Remove unused 'time' import.
194447d to
b603b80
Compare
|
Fixed. Added a |
Summary
Per-thread read-only SQLite connections for WAL-mode recall reads, bypassing
self._lockso recall/browse queries never convoy behind writer flushes.Changes (salvage of #65541 by @Soju06 + follow-up fixes)
_get_read_conn()/_read_ctx()context manager, per-threadmode=roconnections under WAL, converts 8 recall methods (get_session, get_session_by_title, resolve_session_by_title, list_sessions_rich, get_messages, get_messages_around, get_anchored_view, search_messages) fromself._lockto_read_ctx, graceful fallback to locked path under non-WAL or read-conn failure, close() cleanup._get_read_connthrough_connect_tracked_dbso read-only connections are registered with the POSIX lock-safety guard (connect_tracked), matching the writer and existing read-only paths._search_unindexed_gap,_run_trigram_search, CJK-bigram FTS search, andget_metato_read_ctx— pure SELECT queries still takingself._lock.@pytest.mark.requires_walto 5 tests that assume WAL is active (Hermes disables WAL on SQLite < 3.51.3).Validation
Closes #65541