perf: search_symbols ranks by per-row correlated COUNT(*) over the full prefix match set #19
Labels
No labels
code-review
correctness
dos
performance
security
severity/high
severity/low
severity/medium
tech-debt
Kind/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
h-dv/code-index#19
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
search_symbolsranks results by a correlatedref_countsubquery evaluated for every symbol matching the prefix, beforeLIMIT. For a short/broad prefix ("a","get") on a large repo this fires O(prefix_matches) index-count subqueries plus a full in-memory sort — cost scales with prefix breadth, not the requestedlimit.Where
crates/daemon/src/local_index.rs:552(ranked-page query) —(SELECT COUNT(*) FROM refs r WHERE r.target_id = s.id) AS ref_count, thenORDER BY ref_count DESC(line 559),LIMIT/OFFSET(563). No minimum-prefix guard: pattern isformat!("{query}%")(461) and the MCP handler only clampslimitto 1..100 (crates/mcp-server/src/server.rs:2246).Severity
Medium.
idx_refs_target(m0001) makes each COUNT a cheap index range-count, not a scan, so it's a latency spike for broad short prefixes on large indexes rather than a severe cost. Runs under the connection checkout.Fix options
ref_countas a maintained column onsymbols(updated during resolve, or refreshed per batch) with an index, so ranking reads a stored integer. Requires a new migration (m0012) + writer maintenance — migrations m0001–m0011 are frozen, so this is additive.Prefer (1) for a permanent fix. Surfaced by the v0.5.8 ultradeep review (performance dimension, CONFIRMED).
Acceptance
search_symbolsno longer evaluates a per-row subquery over the whole candidate set.bench_search_symbols+ smoke tests stay green.Implemented on
fix/ultradeep-review-findings(commit3bfd136), via fix option 1 (materialized column).search_symbolsno longer evaluates the per-row correlated(SELECT COUNT(*) FROM refs WHERE target_id = s.id)over the whole prefix-match set. A storedsymbols.ref_countcolumn (migration m0012) is read directly by the three ranking sites (search_symbols page, repo_map empty-graph degrade, changed_symbols select).The indexer keeps it current in
recompute_ref_counts, called in the same transaction asresolve_ref_targets(the only placetarget_ids change), via a single aggregate pass —GROUP BY target_idonidx_refs_target,UPDATE...FROMjoined tosymbolsby PK — O(refs), not per-symbol. A reader never sees stale counts.m0012 backfills existing DBs with the same aggregate;
CURRENT_VERSION → 12(frozen once shipped).Acceptance: ranking order preserved (
s.id ASCtiebreak unchanged);bench_search_symbols+ smoke tests stay green. Tests:m0012_backfills_ref_count_from_resolved_refs(upgrade backfill, NULL targets excluded) +search_symbols_ranks_by_materialized_ref_count(e2e: a called symbol outranks its never-called same-prefix sibling through real indexing). Closing.