perf(gateway): 10x faster cold project grouping (3.3s → 0.3s) (#82472)
* perf(gateway): stop spawning git for paths that cannot answer The project tree probes every distinct session cwd, and on a long-lived history most of those directories are deleted worktrees — `git -C` there can only fail, at the price of a fork each. Stat first. The second elision is `common_repo_root`: only repos have a common dir, and the parallel warm never covers that probe because `resolve()` reaches it only for cwds that already resolved. Every non-repo cwd was therefore paying a serial `git` spawn on the discovery pass. * perf(gateway): quit reading system prompts the project tree discards `_project_tree_row` keeps about eighteen fields and drops the rest, but the query behind it selected `s.*` plus the resolved system prompt — 37MB of blob per build on my session history, read out of the B-tree and then thrown away. * perf(gateway): warm every path the project tree will resolve The warm covered session cwds, but build_tree also resolves each declared project folder and each discovered repo root. Those were the last probes running one directory at a time while the sidebar showed a skeleton.
This commit is contained in:
commit
2446c8bb67
|
|
@ -146,6 +146,65 @@ def test_warm_roots_probes_in_parallel_and_fills_the_cache(monkeypatch):
|
|||
assert live["calls"] == before
|
||||
|
||||
|
||||
def test_missing_directory_costs_no_subprocess(monkeypatch):
|
||||
# Deleted worktrees dominate a long session history's cwds, and `git -C` on
|
||||
# one can only fail — so it must never reach the fork.
|
||||
from tui_gateway import git_probe
|
||||
|
||||
def boom(*_a, **_kw):
|
||||
raise AssertionError("spawned git for a directory that does not exist")
|
||||
|
||||
monkeypatch.setattr(git_probe, "bounded_git_probe", boom)
|
||||
|
||||
assert git_probe.run_git("/gone/worktree", "rev-parse", "--show-toplevel") == ""
|
||||
|
||||
|
||||
def test_non_repo_cwd_is_not_probed_for_a_common_dir(monkeypatch, tmp_path):
|
||||
# `warm_roots` only reaches `common_repo_root` for cwds that ARE repos, so a
|
||||
# common-dir probe here is one the warm can't absorb: it runs serially on
|
||||
# the discovery pass, once per non-repo cwd.
|
||||
from tui_gateway import git_probe
|
||||
|
||||
git_probe.invalidate()
|
||||
asked = []
|
||||
|
||||
def probe(cwd, *args):
|
||||
asked.append(args[-1])
|
||||
return "" # not a repo, whatever we ask
|
||||
|
||||
monkeypatch.setattr(git_probe, "run_git", probe)
|
||||
|
||||
assert git_probe.common_repo_root(str(tmp_path)) == ""
|
||||
assert asked == ["--show-toplevel"]
|
||||
|
||||
|
||||
def test_tree_build_warms_every_path_it_will_resolve(monkeypatch, tmp_path):
|
||||
# build_tree resolves declared project folders and discovered repo roots as
|
||||
# well as session cwds. Anything left out of the warm is probed one
|
||||
# directory at a time while the sidebar shows a skeleton.
|
||||
from tui_gateway import git_probe
|
||||
|
||||
repo = tmp_path / "repo"
|
||||
(repo / ".git").mkdir(parents=True)
|
||||
_call("projects.create", {"name": "Repo", "folders": [str(repo)]})
|
||||
|
||||
warmed: list[str] = []
|
||||
real_warm = git_probe.warm_roots
|
||||
|
||||
def recording_warm(cwds, **kw):
|
||||
paths = list(cwds)
|
||||
warmed.extend(paths)
|
||||
return real_warm(paths, **kw)
|
||||
|
||||
monkeypatch.setattr(git_probe, "warm_roots", recording_warm)
|
||||
|
||||
server._build_project_tree(
|
||||
server._get_db(), preview_limit=3, hydrate=False, session_limit=5, include_discovered=True
|
||||
)
|
||||
|
||||
assert str(repo) in warmed
|
||||
|
||||
|
||||
def test_create_list_roundtrip(tmp_path):
|
||||
created = _call("projects.create", {"name": "Demo", "folders": [str(tmp_path)], "use": True})
|
||||
assert created["project"]["slug"] == "demo"
|
||||
|
|
|
|||
|
|
@ -50,7 +50,11 @@ def run_git(cwd: str, *args: str) -> str:
|
|||
session readiness when a killed git left a suspended descendant holding the
|
||||
pipe handles (issue #68609).
|
||||
"""
|
||||
if not cwd:
|
||||
if not cwd or not os.path.isdir(cwd):
|
||||
# `git -C` on a directory that no longer exists can only fail, and it
|
||||
# fails at the price of a fork. Deleted worktrees dominate the cwds a
|
||||
# long-lived session history hands us, so the stat pays for itself many
|
||||
# times over on every project-tree build.
|
||||
return ""
|
||||
return bounded_git_probe(["git", "-C", cwd, *args], timeout=_GIT_TIMEOUT)
|
||||
|
||||
|
|
@ -153,6 +157,13 @@ def common_repo_root(cwd: str) -> str:
|
|||
if not cwd:
|
||||
return ""
|
||||
|
||||
# No work tree, nothing to fold. Reading the (warmed, negative-cached)
|
||||
# toplevel first spares every non-repo cwd a second `git` spawn — one the
|
||||
# parallel warm can never absorb, since `resolve()` only reaches here for
|
||||
# cwds that ARE repos.
|
||||
if not repo_root(cwd):
|
||||
return ""
|
||||
|
||||
def _probe() -> str:
|
||||
gitdir = run_git(cwd, "rev-parse", "--path-format=absolute", "--git-common-dir")
|
||||
if gitdir:
|
||||
|
|
|
|||
|
|
@ -11772,6 +11772,10 @@ def _project_tree_inputs(
|
|||
include_children=False,
|
||||
exclude_sources=_PROJECT_TREE_EXCLUDED_SOURCES,
|
||||
include_archived=False,
|
||||
# `_project_tree_row` keeps ~18 fields and drops the rest, so selecting
|
||||
# the system-prompt blob only to discard it costs tens of MB of B-tree
|
||||
# reads per build on a long-lived database.
|
||||
compact_rows=True,
|
||||
)
|
||||
sessions = [_project_tree_row(r) for r in rows]
|
||||
# Parallel-warm the git cache so build_tree's resolver reads it instead of
|
||||
|
|
@ -11838,6 +11842,13 @@ def _build_project_tree(
|
|||
sessions, projects, discovered, active_id = _project_tree_inputs(
|
||||
db, session_limit, include_discovered=include_discovered
|
||||
)
|
||||
# build_tree resolves every declared project folder and every discovered
|
||||
# repo root too, and those paths are not session cwds — without this they
|
||||
# are the one part of the build still probing git one directory at a time.
|
||||
git_probe.warm_roots(
|
||||
[str(f.get("path") or "") for p in projects for f in (p.get("folders") or [])]
|
||||
+ [str(r.get("root") or "") for r in discovered]
|
||||
)
|
||||
tree = project_tree.build_tree(
|
||||
projects,
|
||||
sessions,
|
||||
|
|
|
|||
Loading…
Reference in New Issue