PR: zlib-ng/zlib-ng#2281 — [CI] Cache Ubuntu .deb packages to speed up installing dependencies.
Follow-up to https://gist.github.com/nmoinvaz/978f248ea7d528c954e3d52fb8dc99c0 — measuring the v2 design after the author refactored into composite actions and adopted the matrix.packages gate.
v2 is a clean win across the board. Total apt-install time across Ubuntu jobs is now −71% vs. develop, up from −47% in v1. Default-only jobs no longer pay cache overhead, and warm cache hits skip the write-back step entirely.
-
Composite actions. Cache logic is extracted into
.github/actions/restore-apt-cacheand.github/actions/update-apt-cache. Bothcmake.ymlandconfigure.ymlinvoke them viauses: ./.github/actions/..., so the gating logic lives in one place. -
Gate on
matrix.packages.cmake.ymlnow usesif: runner.os == 'Linux' && matrix.packages && !contains(matrix.os, 'z15'). Default-only jobs (libgtest-dev libbenchmark-dev) skip the cache machinery entirely, matching the pattern already inconfigure.yml. -
Skip cache write-back on exact hits. The
Update package cachestep is conditioned onsteps.restore-apt-cache.outputs.cache-hit != 'true'. When the cache key matches exactly, it skips the autoclean/autoremove/cp dance — those took ~10–15s per cross-arch job in v1 and were redundant on warm hits.
Same as before: sum dependency-install steps per Ubuntu job, compare against develop baseline.
- develop: run
25509964615(HEAD5e4c65ab) - v1 PR: run
25513495567(HEAD620ca91f) - v2 PR: run
25524841903(HEAD381ed1e7)
Step set per run:
- develop:
Install packages (Ubuntu) - v1:
Get cache key (Ubuntu)+Load package cache (Ubuntu)+Install packages (Ubuntu)+Post Load package cache (Ubuntu) - v2:
Restore cached packages (Ubuntu)+Install packages (Ubuntu)+Update package cache (Ubuntu)+Post Restore cached packages (Ubuntu)
| Run | Total apt time | vs. develop |
|---|---|---|
| develop | 6,413s | — |
| v1 (initial PR) | 3,420s | −46.7% |
| v2 (composite + gate + skip-on-hit) | 1,862s | −71.0% |
v2 saved another ~26 minutes over v1. Most of that came from skip-update-on-hit: cross-arch jobs with large package sets no longer redo autoclean/cp on warm runs.
| Bucket | develop | v1 | v2 | v1 → v2 |
|---|---|---|---|---|
Has explicit packages: (33 jobs) |
6,012s | 2,928s | 1,468s | −1,460s |
| Default-only (23 jobs) | 401s | 492s | 394s | −98s |
The default-only bucket is now slightly under develop — confirming the matrix.packages gate is doing exactly what was intended: removing cache overhead from jobs that wouldn't benefit. The +91s of cumulative overhead v1 was paying on those is gone.
The v1 outliers I'd flagged as runner variance (not cache effects) all fixed themselves once warm-cache hits skip write-back:
| Job | dev | v1 | v2 |
|---|---|---|---|
| Pigz / Ubuntu Clang No Threads | 21s | 674s | 18s |
| Package Check / Ubuntu GCC PPC64LE | 33s | 307s | 19s |
| Pigz / Ubuntu Clang No Optim | 682s | 117s | 18s |
| Ubuntu GCC AARCH64 Compat UBSAN | 23s | 44s | 18s |
| Ubuntu GCC SSE4.2 UBSAN | 22s | 45s | 19s |
| Ubuntu GCC ASAN | 15s | 30s | 13s |
A handful went up v1 → v2 in the +5–25s range (e.g. Ubuntu GCC ARM HF ASAN 37→51s, Ubuntu GCC S390X 22→39s). These look like ordinary mirror/runner variance, not a structural problem.
Two different numbers depending on what you're measuring.
- Apt step alone: ~76 min saved per workflow run (107.1 min → 31.2 min summed across Ubuntu jobs).
- All Ubuntu jobs total: ~101 min — but runner/QEMU variance dominates beyond the apt step, so don't trust this number too far.
The 76 min figure is the clean, attributable savings.
- Slowest Ubuntu job: 32.2 min → 29.7 min (~2.5 min faster).
- Ubuntu jobs are usually not the workflow's long pole anyway — Windows MSVC and macOS Intel jobs often run longer. Typical wall-clock improvement is 2–5 min per run.
In the develop baseline, 8 Ubuntu jobs spent more than 5 minutes each just on apt-install because of slow/flaky mirrors:
| Job | apt time on develop |
|---|---|
| CMake / Ubuntu GCC RISC-V | 11.8 min |
| Pigz / Ubuntu Clang No Optim | 11.4 min |
| CMake / Ubuntu Clang PPC64LE Power9 | 9.5 min |
| Configure / Ubuntu GCC PPC | 7.0 min |
| Configure / Ubuntu GCC PPC64 | 6.4 min |
| Package Check / Ubuntu GCC MIPS | 5.7 min |
| CMake / Ubuntu GCC MIPS | 5.7 min |
| Package Check / Ubuntu GCC MIPS64 | 5.7 min |
In v2, all of these are 17–20s. The cache turns a tail-latency disaster into a flat ~20s cost. Even when it doesn't save much wall-clock on a clean day, it removes the worst-case spikes that occasionally make the whole workflow look broken.
Headline: ~76 minutes of compute saved per workflow run, plus elimination of multi-minute apt-mirror tail latencies.
The v2 design is the right one. Composite actions for reuse, gate on matrix.packages, skip cache-update on exact hit. Apt-step time is now 71% below develop, default-only jobs no longer pay overhead, and the workflow files stay readable. Ship it.