Skip to content

Instantly share code, notes, and snippets.

@nmoinvaz
Last active May 7, 2026 23:58
Show Gist options
  • Select an option

  • Save nmoinvaz/c3f096b565323db64e197c09fa961018 to your computer and use it in GitHub Desktop.

Select an option

Save nmoinvaz/c3f096b565323db64e197c09fa961018 to your computer and use it in GitHub Desktop.
zlib-ng PR #2281 — apt-cache v2 results: composite actions + matrix.packages gate

zlib-ng PR #2281 — apt-cache v2 results

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.

TL;DR

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.

What changed in v2

  1. Composite actions. Cache logic is extracted into .github/actions/restore-apt-cache and .github/actions/update-apt-cache. Both cmake.yml and configure.yml invoke them via uses: ./.github/actions/..., so the gating logic lives in one place.

  2. Gate on matrix.packages. cmake.yml now uses if: 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 in configure.yml.

  3. Skip cache write-back on exact hits. The Update package cache step is conditioned on steps.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.

Methodology

Same as before: sum dependency-install steps per Ubuntu job, compare against develop baseline.

  • develop: run 25509964615 (HEAD 5e4c65ab)
  • v1 PR: run 25513495567 (HEAD 620ca91f)
  • v2 PR: run 25524841903 (HEAD 381ed1e7)

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)

Apt-step time across matched Ubuntu jobs

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.

By bucket

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.

Notable per-job swings v1 → v2

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.

Time saved per workflow run

Two different numbers depending on what you're measuring.

Cumulative compute time (what GitHub Actions bills for)

  • 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.

Wall-clock developer wait time

  • 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.

The reliability win (harder to quantify, possibly the biggest deal)

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.

Bottom line

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment