perf: cache list_files/branch walks that ran on every request#1331
perf: cache list_files/branch walks that ran on every request#1331unsupo wants to merge 1 commit into
Conversation
Two read-only server hot paths re-did expensive filesystem/git work on every call, dominating latency on large repos: - FileListingService._collect_files walked the FULL repo tree (rglob + a stat and an is_indexed check per file) on every list_files/browse request. Split into a short-TTL per-repo_path cache wrapper + _collect_files_uncached, and hoisted the indexable-extensions lookup out of the per-file loop (it was one get_config() call PER FILE via _is_file_indexed()). - BranchService.list_branches read .commit for every local and remote ref (GitPython loads each commit object) on every get_branches call. Split into a short-TTL cache keyed on (codebase_dir, include_remote) + _list_branches_uncached. Both caches are eventually-consistent within a 30s TTL and bounded (LRU). Keyed so immutable versioned golden-repo snapshots miss naturally on refresh. Repeated calls (a client paging a repo, or many agents hitting the same repo) reuse the walk instead of re-running it. Adds regression tests for both caches; existing file/branch suites unchanged.
|
Reviewed via /implement-backlog and integrated into Code review confirmed the cluster-invariant is satisfied (read-only listings, bounded 30s staleness, no auth-bearing data — the same carve-out as query_path_cache) and immutable-snapshot keying is correct. Three findings were addressed before merge:
Gates: fast-automation 12389/0; server-fast clean (2 failures were pre-existing flakes unrelated to this PR — Follow-up note (LOW, not blocking): for This will show as merged once |
What
Two read-only server hot paths re-ran expensive filesystem/git work on every request, dominating latency on large repos:
FileListingService._collect_fileswalked the full repo tree (rglob+ a stat and anis_indexedcheck per file) on everylist_files/browsecall. Split into a short-TTL per-repo_pathcache +_collect_files_uncached, and hoisted the indexable-extensions lookup out of the per-file loop (it was oneget_config()call per file via_is_file_indexed()).BranchService.list_branchesread.commitfor every local and remote ref (GitPython loads each commit object) on everyget_branchescall. Split into a short-TTL cache keyed on(codebase_dir, include_remote)+_list_branches_uncached.Why
On a large monolith these listings ran in the tens of seconds and were recomputed per request, so N clients/agents hitting the same repo each paid the full walk. Read-only listings where a short staleness window is acceptable.
Behavior / safety
Tests
Regression tests for both caches (cache hit avoids re-walk, key isolation, non-git repo,
is_indexedhoist correctness). Existing file/branch suites pass unchanged.ruff/ruff format/mypyclean.🤖 Generated with Claude Code