perf(s3): ranged pack reads + adaptive ObjectResolver (no whole-pack fetch for point reads) #44

merged colechristensen cole.christensen@gmail.com wants to merge feat/s3-pack-read-perf into main

Why

Investigating anvil’s Filesystem→S3 git cutover (anvil #84/#325), a single-object read on a packed repo downloaded the entire pack and parsed the whole index. Cold read on S3 (134k-object repo, 36 MiB pack): ~1300 ms — making the common web workload (view a file/commit/PR) unusable on S3.

What

  1. Index.lookup_in_raw/2 — O(log n) point lookup from raw .idx bytes (fanout + binary search), no full parse. Cross-checked vs parse+lookup for every SHA of a real index.
  2. get_pack_range/5 optional storage callback — S3 HTTP Range GET, Filesystem pread, Memory slice.
  3. Reader.read_object_ranged/3 — read one object via windowed ranged fetches, resolving OFS/REF delta bases with more ranged fetches; truncation detected via the header’s declared uncompressed size. Cross-checked vs whole-pack read for every object incl. delta chains + a >window object.
  4. Adaptive ObjectResolver — point reads go ranged (O(1) round-trips, no whole-pack); a pack escalates to whole-pack after 64 reads/process so clone/walk still pay one fetch.
  5. Fixed clear_pack_cache/0 (was a no-op for 3-tuple-keyed caches).

Results (S3-packed via MinIO, 134k-object repo)

workload before after
single-object cold read 1304 ms 38 ms (matches S3-loose)
tree HEAD cold 107 ms 18 ms
full log walk (20,797) 10.8 s 4.6 s
clone (11k objects) 14.0 s 4.2 s

Tests

Full suite 979 passed; the Memory backend now advertises get_pack_range, so the whole suite (graph/diff/walk/upload-pack) exercises the ranged path. New: index/reader cross-checks + window growth + S3 Range-GET against MinIO.

Follow-up

On real S3 the per-pack .idx download becomes the cold-read long pole; a cross-request index cache would close it.

🤖 Generated with Claude Code

Created Jun 22, 2026 at 14:12 UTC | Merged Jul 01, 2026 at 19:43 UTC by colechristensen cole.christensen@gmail.com