turbo
f0a0d775 - perf: Build cache archives once for local and remote writes (#14012)

Commit
8 days ago
perf: Build cache archives once for local and remote writes (#14012) ## Summary Closes TURBO-5986. Builds on the spooled-artifact transfer work merged in #14011. With both local and remote cache writes enabled, output files were opened, read, and compressed twice: `FSCache::put` built its own archive while `HTTPCache::put` built a second copy for upload. The fetch direction had the mirror problem: a remote hit restored files and then `FSCache::put` re-read and re-compressed every restored output to populate the local cache. ## Changes - New shared `ArtifactBody` (in `artifact_body.rs`): the canonical compressed archive, held in memory below 8 MiB and spooled to an anonymous temp file above it. Built once via `ArtifactBody::from_files`; every consumer gets a fresh positioned reader or bounded stream over identical bytes. - `CacheMultiplexer::put` with local+remote writes now builds the archive once, atomically installs it into the local cache (temp + rename, 1 MiB-buffered copy, same publication semantics as `CacheWriter::create`), and uploads the same bytes. Single-destination paths are unchanged. - `CacheMultiplexer::fetch` on a remote hit with local writes installs the *downloaded* archive bytes (signature verification gates any restore or local install) instead of re-encoding the restored files. A rejected download can never become a local hit. - `HTTPCache::put` splits into archive construction and `put_body`; `fetch_with_archive` returns the verified body alongside restored files. Signing is unchanged: the tag covers exactly the uploaded bytes. ## Benchmarks End-to-end through `AsyncCache` against the repo's mock remote as a separate process; debug build; macOS. Five interleaved runs per scenario (order alternated), medians reported. Harness was temporary and is not committed. Debug-build disk I/O on this machine is noisy; the many-small-files case is the most deterministic and the multi-hundred-MiB case is dominated by page-cache variance, so phase timings from an instrumented run are included for the large case. | Scenario | Phase | Before | After | Change | | --- | --- | --- | --- | --- | | 10,000 × 100 B files | put (local+remote) | 1,980 ms | 916 ms | −54% | | 10,000 × 100 B files | remote-hit fetch + local install | 2,125 ms | 1,395 ms | −34% | | 100 × 1 MiB files | put (local+remote) | 207 ms | 148–263 ms (noisy) | ~wash to −29% | | 100 × 1 MiB files | remote-hit fetch + local install | 254 ms | 197 ms | −22% | | 1 × 512 MiB file | put (local+remote) | 3,460 ms | 2,498 ms | −28% | | 1 × 512 MiB file | remote-hit fetch + local install | 4,156–4,530 ms | 3,222–3,672 ms | −14 to −25% | Instrumented phase breakdown, 512 MiB remote-hit fetch: local archive install (buffered copy of verified bytes) 338–641 ms, replacing a full read + zstd re-encode of restored outputs (~1.1–1.2 s in the same environment). Restore time dominates and is identical in both paths. ## Correctness preserved - Local publication stays atomic (temp file + rename); metadata/manifest sidecars are written exactly as before. - Signing still covers exactly the uploaded bytes; retries after token refresh resend byte-identical content. - New tests: local install and remote upload are byte-identical after a combined put; a remote-hit fetch installs byte-identical archive bytes locally. - `cargo test -p turborepo-cache`: 159 passed, 0 failed. `cargo clippy -p turborepo-cache`: clean.
Author
Parents
Loading