ly-wang19 346164d740
perf(runtime): index MemoryRunStore by thread_id to avoid O(n) scans (#3562)
* perf(runtime): index MemoryRunStore by thread_id to avoid O(n) scans

MemoryRunStore is the default run backend (database.backend=memory) and backs
RunManager.list_by_thread, which calls it on every thread-runs query to hydrate
persisted runs. list_by_thread scanned every run in the store (O(total runs))
to filter by thread_id, so listing one thread's runs got linearly slower as
unrelated runs accumulated across all threads.

Add a thread_id -> insertion-ordered run_id set secondary index, maintained in
lockstep with _runs in put()/delete(), and use it in list_by_thread for an
O(runs-in-thread) lookup. This mirrors the index RunManager already keeps over
its own in-memory records (#3499); the store extraction — whose docstring notes
it is "Equivalent to the original RunManager._runs dict behavior" — did not
carry the index across.

Behavior is unchanged: same user_id filtering, newest-first ordering, and limit.
The store runs each method without awaits on the event loop, so the index and
_runs stay consistent without a lock.

Extends tests/test_persistence_scaffold.py::TestMemoryRunStore with coverage for
unknown-thread, newest-first ordering, limit, and index cleanup on delete
(including empty-bucket removal).

* perf(runtime): route aggregate_tokens_by_thread through the thread index too

list_by_thread already uses the _runs_by_thread index this PR adds, but
aggregate_tokens_by_thread (the /token-usage endpoint) still scanned every run
in the process to pick out one thread's runs. Route it through the same index
for an O(runs-in-thread) lookup, completing the thread-scoped read coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: ly-wang19 <ly-wang19@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 10:57:45 +08:00
..