Use per-branch Go build cache with main fallback

What does this MR do?

Follow-up to !6906 (merged)

Updates the Go build caching strategy such that:

  1. Cache keys are per-branch, rather than shared across entire scopes (protected/non-protected).
  2. main is used as a fallback key to warm the cache for non-main branches.

Why was this MR needed?

The previous approach allowed the Go build cache to be shared across all branches in a given scope (i.e., protected or non-protected). This allowed for warm caches across virtually all branches (e.g., main, release branches, feature branches) but accumulated distinct build cache entries over time, because every branch in the scope writes its build results back into the one shared cache. This was an anticipated, accepted trade-off given Go's built-in 5-day trim. However, the cache has grown faster than that trade-off assumed (from ~6.47 GB to ~9.29 GB within a day of the previous change), so this MR bounds it. Each branch now writes only its own cache, so the shared cache no longer accumulates every branch's churn. This reduces the risk of caches outgrowing the disk space available to complete a job and prevents performance degradation due to more time spent processing and transferring large caches.

The desirable cache warming behaviour on all branches is retained through a fallback to main's cache, and Go's automatic trimming of cache entries after 5 days still applies.

One caveat is that branches in the non-protected scope (i.e., a branch not matching a protected pattern like feature/* and not triggered by a Maintainer) will always build cold during their first pipeline run (as no main fallback exists for the non-protected scope). This is a relatively small trade-off, since most MRs are from project maintainers, and all subsequent pipelines after the first on a non-protected scope branch still benefit from a warm cache.

What's the best way to test this MR?

On this MR, the binaries, unit test, and go:lint jobs were each run twice:

Run Expected cache behaviour
1st (cold) restore_cache miss on both the branch key (first run) and the ${JOB}-main fallback (not written until this is merged to main)
2nd (warm) restore_cache hit on the branch-specific key

Post-merge, confirm on each ref type:

Ref type 1st run Subsequent runs
main pulls and pushes its own ${JOB}-main canonical cache same (kept warm)
non-main, protected scope (e.g. feature/*, maintainer-triggered) miss primary, hit ${JOB}-main fallback (warm from main), writes own key hit branch-specific key
non-main, non-protected scope miss primary, miss ${JOB}-main fallback (doesn't exist in this scope), writes own key hit branch-specific key

Also verify main's cache size stops growing quickly post-merge, since branches no longer write into it.

What are the relevant issue numbers?

N/A

Edited by Daniel Keenan

Merge request reports

Loading