perf: graph cache mtime freshness + walk-base-once in fallback #26

merged colechristensen cole.christensen@gmail.com wants to merge graph-cache-freshness-and-fallback-walk-once into main
No CI

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.
Created May 06, 2026 at 00:07 UTC | Merged May 06, 2026 at 01:54 UTC by colechristensen cole.christensen@gmail.com