perf: graph cache mtime freshness + walk-base-once in fallback #26
graph-cache-freshness-and-fallback-walk-once
into main
Two related fixes from investigating fangorn/anvil’s 2.6 s PR-list mount.
Adds Storage.blob_fingerprint optional callback (Filesystem returns mtime+size, others :unsupported) so Graph.Cache.fetch/2 can return :stale when the on-disk graph has been rewritten by a separate VM. Without this, ‘mix anvil.graphs.rebuild’ from a separate shell never reaches the running app’s persistent_term cache — that’s why the rebuild was ‘briefly effective then gone’.
Adds Graph.Fallback.ahead_behind_many/4 (walks ancestors(base) once and reuses it across heads). The old fill_per_head/4 was O(N · |ancestors(base)|) which on fangorn/anvil with ~50 PRs against a few-hundred-commit base was the actual reason the slow path was multi-second, not just slow.
API change: Graph.Cache.fetch/1 -> fetch/2 and put/2 -> put/3 (added fingerprint arg; pass Cache.no_fingerprint() for old behaviour).
Test plan
- 924 tests / 0 failures
- New tests cover walk-once correctness (vs per-call), cache freshness transitions, and an end-to-end “separate-process rewrite triggers reload” integration test.
- mix format –check-formatted clean.
- No new credo issues in changed files.