A team enabled the remote build cache to speed up their slow Gradle build but saw almost no improvement. As the senior engineer, how do you reason about what went wrong and what you measure next?
answer
- build cache = execution only, never configuration
- measure phase split before optimizing
- gradle help --profile isolates configuration
- config-bound -> avoidance/configuration cache
- exec-bound low hits -> cacheable + relocatable
basics
~20 sThe build cache only speeds up execution; if the bottleneck is the configuration phase, caching does nothing. Measure with --profile/--scan to get the config-vs-execution split first; if configuration dominates, the fix is configuration avoidance, not caching.
solid answer
~50 sThe likely mistake is optimizing the wrong phase. The build cache reuses **executed task outputs**, so it only helps the **execution** phase — it cannot reduce **configuration** time, which is paid on every build regardless of caching. If the build was configuration-bound, enabling the cache predictably yields almost nothing. My first move is to measure, not guess: run `gradle help --profile` (a near-no-op isolates configuration) and `gradle build --scan`, then read the per-phase split. If configuration dominates, I look for eager task creation, configuration-time dependency resolution, and heavy plugin/`buildSrc` logic, and the real remedy is configuration avoidance / the configuration cache — a different lever entirely. If execution is actually the bottleneck but cache hits are low, I investigate cacheability: are tasks `@CacheableTask`, are inputs/outputs declared and *relocatable* (no absolute paths), and is the cache key stable across machines? The discipline is: attribute time to a phase first, then apply the matching fix.
code
bash · 5 lines# Decision-driving measurements, in order:
gradle help --profile # is configuration the bottleneck? (near no-op)
gradle build --scan # Performance > Build phases: config vs exec seconds
# If config dominates -> configuration avoidance / configuration cache
# If exec dominates but cache misses -> check @CacheableTask + relocatable I/Ogo deeper
State that the build cache only helps execution, not configuration.
Measure the phase split first and pick the matching lever (avoidance vs cacheability).
Reason end-to-end: attribute time, then diagnose cache hit/miss and relocatability, applying the correct remedy per bucket.
Institutionalize 'measure-before-optimize', track cache hit rates and configuration time as fleet metrics, and govern relocatable inputs across shared plugins/CI.
## The core misconception The build cache stores and reuses the **outputs of executed tasks** keyed by a hash of the task's inputs. By construction it can only shorten the **execution** phase. The **configuration** phase — evaluating scripts and building the task graph — runs on essentially every invocation and is *untouched* by the build cache. So a team that was configuration-bound and reached for the cache optimized the wrong phase and saw no gain. This is the canonical config-vs-execution misdiagnosis. ## Step 1 — measure the phase split Don't theorize; instrument: - `gradle help --profile` — because `help` executes almost nothing, its time is essentially all configuration. A slow result here proves configuration is the problem. - `gradle build --scan` — read **Performance > Build phases** for the seconds in configuration vs execution, and check the suggestions (config-time resolution, cache enablement). ## Step 2a — if configuration dominates The cache is irrelevant. Look for: eager task realization (`tasks.create`/iterating `tasks` and configuring them all), dependency resolution during configuration, and expensive plugin or `buildSrc` work. The fix is **configuration avoidance** (lazy `tasks.register`, `Provider`/`Property`) and potentially the **configuration cache**, which serializes and reuses the task graph so configuration is skipped on subsequent compatible builds. (The mechanics of those fixes are a separate topic; the point is the *lever* is not caching.) ## Step 2b — if execution dominates but cache hits are low Now the cache *should* help, so figure out why it isn't: - Are the heavy tasks annotated `@CacheableTask` (or is `buildCache` even enabled for them)? - Are inputs/outputs fully declared and **relocatable** — no absolute paths, machine-specific values, or undeclared inputs that change the key needlessly across machines? Non-relocatable inputs make remote hits impossible. - Is the cache key stable run-to-run (timestamps, environment, JDK version baked into inputs will bust it)? - Check scan **cache insights** for the hit/miss rate and the reason for misses. ## The transferable principle **Attribute, then fix.** Every Gradle performance effort should start by splitting wall time into configuration vs execution (and within execution, cache hit vs miss). Each bucket has a distinct remedy: configuration → avoidance/configuration cache; execution-with-misses → cacheability/relocatability; execution-cached-but-slow → incremental work, parallelism, or genuinely faster tasks. Spending effort on a bucket that isn't the bottleneck — like this team did — is the most common and most expensive mistake. ```text slow build? -> measure phase split (--profile help / --scan) configuration-bound -> avoidance / configuration cache (NOT the build cache) execution-bound, low hits -> @CacheableTask + relocatable inputs/outputs execution-bound, high hits -> faster/incremental/parallel tasks ```
- Name two reasons a remote cache yields hits locally but misses across machines.Non-relocatable inputs (absolute paths baked into the cache key) and undeclared/volatile inputs like timestamps, hostname, or JDK build version that differ per machine and bust the key.
- If the build is configuration-bound, which Gradle feature actually addresses it?Configuration avoidance (lazy `tasks.register`, Provider/Property) and the configuration cache, which serializes and replays the task graph to skip configuration on subsequent compatible builds — not the build cache.
- How do you confirm cache effectiveness rather than guess?Read the build scan's cache insights / performance section for the hit-vs-miss rate and miss reasons, or compare execution time and FROM-CACHE outcomes across a clean and a warm build.
They bought a faster oven to cut down dinner time, but the delay was actually in deciding the menu every night. The oven (cache) speeds cooking (execution), not planning (configuration).
saying these in an interview costs you the question
- Asserting the build cache reduces configuration time — it never does.
- Enabling caching without first measuring which phase is slow.
- Ignoring relocatability, so the cache works on one machine but never shares across the team/CI.