fix(cli): avoid full part-table scan in session model usage#11893
Conversation
Code Review SummaryStatus: 1 Issue Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
The core fix is sound: scoping the aggregation to the already-resolved family IDs via a concrete Fix these issues in Kilo Cloud Files Reviewed (2 files)
Reviewed by claude-sonnet-5-20260630 · Input: 30 · Output: 10K · Cached: 671K Review guidance: REVIEW.md from base branch |
Benchmark resultsProfiled with the VS Code self-test harness against a real 3.1 GB / 425k-part history DB, identical scenario before and after the change. Root cause confirmed by profilingThe webview renderer is idle-waiting on a cold session open, not CPU-bound. The messages endpoint itself returns in <30ms even for a 4 MB session. The stall came entirely from the per-model token usage query, which is fetched on every session open: its inline recursive family CTE made SQLite
Server endpoint latency
End-to-end cold worktree open in Agent Manager (webview message timeline)
Transcript renders in ~234ms instead of ~2.8s. The token usage header still populates correctly (verified visually), and both model-usage unit tests pass, so aggregation output is unchanged. |
Problem
Opening or switching sessions in the VS Code sidebar and Agent Manager stalls for several seconds before the transcript renders on large histories. The session header's per-model token usage breakdown is fetched on every session open, and its aggregation query re-derives the session family with an inline recursive CTE. SQLite cannot estimate the CTE size and chooses a plan that full-scans the entire
parttable, runningjson_extracton every row. The cost is proportional to total database size rather than the opened session, so it grows past a second on large histories (about 1.2s on a 3 GB, 425k-row database here). Because the query runs synchronously on the single-threaded server, it head-of-line blocks the otherwise fast messages request, so the transcript cannot load until the scan finishes.Change
The family session IDs are already resolved separately just before the usage query runs. This scopes the aggregation to those IDs with a concrete
IN (...)list instead of re-deriving the family inline, which lets the planner seek the existingpart_session_idxand read only the family's own parts. The aggregation output is unchanged.Impact
Session opening no longer blocks on a database-wide scan. The query drops from a full
partscan (seconds, scaling with total history size) to an index seek (milliseconds, scaling with the opened session tree), and it no longer stalls message loading.The regression was introduced by #11608 (the aggregation query) and made visible on the VS Code session-open path by #11746.