Skip to content

CI: merge-queue Haskell builds never hit the dependency cache (cold ~22-min install every run) #911

Description

@mengwong

The pr-checks.yml Haskell dependency cache never hits on merge-queue runs, so every queued PR pays a full cold cabal build of all dependencies (~22 min) on top of the actual build+test.

Evidence (two consecutive merge-queue runs, both cold)

  • pr-81 run — "Restore cached build artifacts" and "Restore cached dependencies" both logged Cache not found for input keys: pr-checks-Linux-ghc-9.10.2-cabal-3.16.1.0-plan-<hash>, pr-checks-Linux-ghc-9.10.2-cabal-3.16.1.0- (and the -dist-<hash> equivalent). Install dependencies: ~22 min (14:48:27 → 15:10:20 UTC), then Build ~7 min, tests ~3 min.
  • pr-82 run (immediately after pr-81 merged) — Install dependencies: 15:24:53 → 15:47:33 = ~22.7 min, again cold. Build ~7 min.

Observed cache keys: pr-checks-Linux-ghc-9.10.2-cabal-3.16.1.0-plan-<plan-hash> (deps) and pr-checks-Linux-ghc-9.10.2-dist-<content-hash> (build artifacts).

Diagnosis

Two compounding causes:

  1. The push-warm races the merge queue. The intent (warm a base cache on push to the integration branch) can't win during a burst of merges: when PR N merges, the push-to-branch warm run starts (and is itself cold the first time, ~30 min), but PR N+1's merge_group run starts immediately and reaches its restore step long before the warm has saved. So it misses. Every PR in the burst is one step ahead of the warm.

  2. merge_group runs can't share caches with each other. Each runs on a distinct gh-readonly-queue/<branch>/pr-<N>-<sha> ref. GitHub Actions scopes cache reads to the current ref + its base branch + the default branch, and a cache saved during a merge_group run is keyed to that ephemeral ref — invisible to the next PR's merge_group run. So queue runs can only restore caches that were saved directly on the integration branch (a push event) or main — never from a sibling queue run.

Net: during any burst of merges, the dependency cache is perpetually cold and each queued PR wastes ~30 min of compute. It would only appear to "work" for isolated merges with idle gaps long enough for a warm to finish.

Suggested fixes (any one helps; combining is best)

  • Split the cache and key deps on the cabal plan hash only. Save a ~/.cabal(store) + dist-newstyle dependencies cache on every push to the integration branch, keyed on the plan.json/cabal.project.freeze hash (stable across PRs that don't change deps). Give merge_group runs a restore-keys prefix (pr-checks-Linux-ghc-9.10.2-cabal-3.16.1.0-plan-) so even a slightly-stale plan restores the bulk of built deps and turns the ~22-min install into a few minutes.
  • Proactively warm via a scheduled job (e.g. hourly/on-merge with concurrency dedupe) so a usable base cache exists before bursts, decoupled from the merge cadence.
  • Optionally have merge_group runs also Save the deps cache under the branch-scoped key as a best-effort, but note the sibling-invisibility above — the branch/push cache is the reliable source.

Workflow: .github/workflows/pr-checks.yml, steps Restore cached dependencies / Install dependencies / Save cached dependencies in the Haskell Build & Test job. Evidence gathered from recent merge-queue runs on the integration branch.

Metadata

Metadata

Assignees

No one assigned

    Labels

    infrastructurebuild, CI, and devopsready-to-startScope is concrete (often spec-backed); ready for an engineer to branch

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions