Infrequently accessed blobsArtifacts evicted from hot tier or not yet promoted.
Cross-workspace historyBuild outputs shared across branches and PRs.
Transparent promotionFrequently read blobs move to hot tier automatically.
PER-ACTION REQUEST FLOW — WARM CACHE
1
Bazel computes action key
Hash of input digests + command + whitelisted env vars. Stable if the build is hermetic.
2
Hot tier AC lookuphit — <1ms
NVMe-backed Cache Volume returns the action result immediately. Steps 3 and 4 are skipped entirely for this action.
3
Cold tier AC lookuponly on miss
Consulted only when the hot tier has no entry — first run after volume creation, or for actions that haven't run since the last cold start.
4
Blob read from hot tier CAS<1ms per file
Artifact bytes read from local NVMe. No network fetch. For a 10MB .rlib, this is ~0.1ms vs ~100ms over GCS.
On a warm cache, steps 2 and 4 handle 80–95% of actions entirely on local NVMe. The cold tier and remote network are only consulted for the delta: actions that genuinely changed since the last committed cache state.
CACHE VOLUME FORK MODEL — WHAT HAPPENS ACROSS RUNS
Parallel PRs each get isolated forks. Writes never collide mid-run.
Failure safety
Exit non-zero = changes discarded. Partial builds never corrupt the committed state.
Last-write-wins
When two jobs both succeed, the later commit wins. Safe because Bazel outputs are deterministic: same inputs always produce identical outputs.
First run is always cold
No committed state exists yet. All actions fall through to the remote cache. Run 2 onwards is where the volume pays off.
The fork model mirrors how Git handles branches — each job gets its own working copy of the last stable state, and merges back only on clean completion. The difference is that Bazel's content-addressed outputs are deterministic, so merge conflicts are structurally impossible.
SAME CACHE BACKEND, TWO ENTRY POINTS
Namespace workspace cache
Hot NVMe tier + cold artifact storage
Local dev
nsc cache bazel setup
CI runner
auto-configured by profile
WHAT LOCAL-TO-CI SHARING MEANS IN PRACTICE
1
CI builds main branch, populates cache
Every action result and blob written by CI is committed to the shared workspace cache. Short-lived credentials scope access to the workspace, not to a specific machine.
The CLI writes a bazelrc pointing at the same Namespace cache endpoint CI used. Short-lived token is generated and expires after a few hours. No long-lived service account key needed.
3
Developer runs bazel build //...
Bazel queries the Namespace cache. Everything CI already built on the latest commit is a hit. No cold compile. The developer gets CI's cache for free.
4
Developer builds a new target locally
With --remote_upload_local_results=true in .bazelrc, the local build writes results back to the shared cache. CI and other developers can now hit those entries too.
No cold builds for developers
Pull a branch that CI already built. First local bazel build hits the cache.
Credential model
Short-lived tokens only. No GCS service account keys to rotate or leak.
Security note: Only enable --remote_upload_local_results=true for CI runs against merged code. For PR CI (untrusted code), use --noremote_upload_local_results to prevent unreviewed code from writing to the shared cache. See Julio Merino's cache poisoning writeup for the attack vector.
CACHE VOLUME SIZE CALCULATOR
When committed cache content exceeds the configured volume size, the next run receives an empty volume: a silent cold start with no warning in CI logs. Size correctly from the start.
Codebase type
Rust monorepo
Go monorepo
JVM (Java / Kotlin / Scala)
Python
Rust monorepo
C++ monorepo
Mixed (Go + Rust)
Target count
5,500
Active engineers
10
VOLUME SIZE ESTIMATE
Estimated output footprint158 GB158 GB for 5,500 Rust targets + 10 engineers writing local results
rlib files and debug symbols are large. Active monorepos exceed 50GB in days.
SIZE OVER TIME — WHAT HAPPENS WITHOUT CORRECT SIZING
Under-sized
133 GB vol.fills in ~6w →exceeds limit→cold start
Correct size
221 GB vol.stable for months →always warm
A 221 GB volume gives 63 GB of headroom above the estimated peak footprint. Monitor actual usage in the Namespace Usage Explorer and adjust if usage consistently exceeds 75% of configured size.