Skip to content

Performance: getDominantFile adds ~5s to every generic explore on large repos #1864

Description

@acniray

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:

  1. 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
  2. Persist the final dominant-file metadata

    • simpler, but needs careful invalidation / refresh semantics after incremental sync
  3. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions