Summary
On a large private repository, generic codegraph explore calls spend most of their time inside QueryBuilder.getDominantFile(). The dominant-file heuristic is query-independent graph metadata, but it is recomputed on every generic explore via a whole-graph aggregation.
This makes exact-file queries fast while generic symbol / caller / natural-language / flow queries pay a ~5s fixed cost.
Environment
- CodeGraph:
1.6.0
- Repository size: several thousand indexed files
- Storage was also tested on tmpfs; storage was not the bottleneck
Benchmark
Before bypassing getDominantFile():
===== exact file query =====
run1 real=0.47
run2 real=0.44
run3 real=0.45
===== generic symbol query =====
run1 real=5.44
run2 real=5.27
run3 real=5.50
===== caller-style query =====
run1 real=5.80
run2 real=5.60
run3 real=5.62
===== natural-language behavior query =====
run1 real=5.43
run2 real=5.41
run3 real=5.49
===== cross-module flow query =====
run1 real=6.30
run2 real=6.36
run3 real=6.10
CPU usage for the slow cases was ~101-103%, i.e. effectively one core.
Profile
V8 profiling points very strongly at getDominantFile():
[Bottom up (heavy) profile]
4545 81.4% UNKNOWN
4181 92.0% JS: ^all .../db/sqlite-adapter.js
4029 96.4% JS: ~getDominantFile .../db/queries.js
4029 100.0% JS: ~findRelevantContext .../context/index.js
619 11.1% __memcmp_evex_movbe
612 99.0% JS: ~getDominantFile .../db/queries.js
The current query is:
SELECT n.file_path AS file_path, COUNT(*) AS edge_count
FROM edges e
JOIN nodes n ON e.source = n.id
JOIN nodes m ON e.target = m.id
WHERE n.file_path = m.file_path
GROUP BY n.file_path
ORDER BY edge_count DESC
LIMIT 20
So each generic explore scans / joins / groups the graph again even though the result does not depend on the user query.
A/B test
I temporarily changed getDominantFile() to return null and reran the same query classes:
generic symbol query
real=0.66
caller-style query
real=0.90
natural-language behavior query
real=0.74
cross-module flow query
real=1.47
So removing only this heuristic from the request hot path improves these queries by roughly 4-8x.
Why this matters
codegraph_explore is the important unified tool for agents. Splitting agents onto narrower tools is not a good substitute because they may fall back to generic file-search/read workflows. Keeping explore fast is therefore important for the intended workflow.
Possible solutions
I would prefer to keep the heuristic, but move its expensive aggregation off the query path. A few possible approaches:
-
Materialize per-file internal edge counts
- maintain a small derived table keyed by
file_path
- full index rebuilds it once
- incremental sync refreshes affected files
getDominantFile() becomes a cheap ORDER BY edge_count DESC LIMIT 20
- preserves exact freshness for small repos and avoids stale ranking metadata
-
Persist the final dominant-file metadata
- simpler, but needs careful invalidation / refresh semantics after incremental sync
-
Short-term guard for large repos
- skip the dominant-file boost above some repo-size threshold (for example 500 files)
- avoids the multi-second query penalty, but loses the heuristic on large repos
The first option seems the most complete long-term fix, but it is a larger schema / lifecycle change, so I wanted to report the profiling data first and let maintainers decide the preferred direction.
Expected result
Generic explore should not perform a whole-graph aggregation whose result is independent of the query. Ideally the dominant-file heuristic remains available while query-time latency stays close to the ~0.7-1.5s observed when that aggregation is bypassed.
Summary
On a large private repository, generic
codegraph explorecalls spend most of their time insideQueryBuilder.getDominantFile(). The dominant-file heuristic is query-independent graph metadata, but it is recomputed on every generic explore via a whole-graph aggregation.This makes exact-file queries fast while generic symbol / caller / natural-language / flow queries pay a ~5s fixed cost.
Environment
1.6.0Benchmark
Before bypassing
getDominantFile():CPU usage for the slow cases was ~101-103%, i.e. effectively one core.
Profile
V8 profiling points very strongly at
getDominantFile():The current query is:
So each generic explore scans / joins / groups the graph again even though the result does not depend on the user query.
A/B test
I temporarily changed
getDominantFile()toreturn nulland reran the same query classes:So removing only this heuristic from the request hot path improves these queries by roughly 4-8x.
Why this matters
codegraph_exploreis the important unified tool for agents. Splitting agents onto narrower tools is not a good substitute because they may fall back to generic file-search/read workflows. Keepingexplorefast is therefore important for the intended workflow.Possible solutions
I would prefer to keep the heuristic, but move its expensive aggregation off the query path. A few possible approaches:
Materialize per-file internal edge counts
file_pathgetDominantFile()becomes a cheapORDER BY edge_count DESC LIMIT 20Persist the final dominant-file metadata
Short-term guard for large repos
The first option seems the most complete long-term fix, but it is a larger schema / lifecycle change, so I wanted to report the profiling data first and let maintainers decide the preferred direction.
Expected result
Generic
exploreshould not perform a whole-graph aggregation whose result is independent of the query. Ideally the dominant-file heuristic remains available while query-time latency stays close to the ~0.7-1.5s observed when that aggregation is bypassed.