You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
ci: build the guest-artifact components in parallel and download only what each job uses #703
The shared guest-artifact job restores or builds the guest components one after another, and every consumer downloads all of them. Once #699 and #702 have reduced cold builds, measure what remains, and build the components in parallel if cold builds still matter.
Today
build-guest-artifacts restores or builds the kernel, Alpine, Ubuntu, Azure Linux, and Ubuntu distro layer sequentially in the artifacts job. Warm runs take about 0.4 minutes. In CI: maximize workflow parallelism and reduce turnaround time #430's baseline, 44 of the 73 runs built cold, with a median of 5.3 minutes and a maximum of 11.3.
Every consumer downloads the whole guest-artifacts artifact, although, for example, the OpenVMM VMM tests boot only the kernel and the Alpine initramfs.
The debug kernel builds separately, in the debug-kernel job.
Per-component measurements, estimates for #699 and #702, and the split decision
Measured on 2026-10-21 for #711. Data: the jobs API of the 449 CI runs created from 2026-10-13 to 2026-10-21 13:55 UTC, and the logs of the 202 artifacts and debug-kernel jobs that ran longer than 45 s since 2026-10-15. The jobs API does not list a composite action's steps, so the per-component times come from the step groups in the job logs.
The current guest build, with the Rust guest-binary Docker stage and the -v2 image keys, started with #561, merged 2026-10-19 10:52 UTC (#561's own pull-request runs, from 2026-10-16, use it too). The numbers below cover the 200 artifacts jobs since then: 47 dev pushes and 153 pull-request runs.
Cache misses and build time by component
Component
Misses (of 200)
Build p50
p90
max
Kernel
13
5.69 min
5.89
6.00
Alpine initramfs (with the shared base and Rust guest-binary stages)
151
0.85
1.00
1.16
Ubuntu initramfs
157
0.58
0.65
1.58
Ubuntu determinism check (runs if the initramfs or the distro layer misses)
159
2.90
3.10
3.39
Ubuntu distro layer
159
0.26
0.28
0.82
Azure Linux initramfs
156
0.37
0.41
0.47
Cache saves, all components
159
0.10
0.15
0.51
Whole job: 159 of 200 cold (80%; 36 of 47 pushes, 123 of 153 pull-request runs), p50 5.32 min, p90 5.97, max 11.43. Warm (41): p50 0.32. Cold with a warm kernel (146): p50 5.28, p90 5.73; with a cold kernel (13): p50 10.28. Queue time: p50 0.07 min, max 0.18.
Miss patterns: Alpine + Ubuntu + distro + Azure Linux with a warm kernel 138; all five 13; Ubuntu + distro + Azure Linux 5; distro only 2; Ubuntu + distro 1.
Docker stages in the cold builds: base packages 19 s; Rust guest binaries about 41 s (image pull 9, packages 9, build-guest-binaries 23), which BuildKit runs beside the base stage; Alpine initramfs 4.5 s; Ubuntu initramfs 34 s; Ubuntu determinism 165 s; distro layer 15 s; Azure Linux initramfs 19 s; kernel 323 s.
Before the Rust stage (17 cold jobs with logs, 2026-10-15 to 10-19, -v1 keys): kernel 5.6 min, Alpine 0.13, Ubuntu 0.63, determinism 1.77, distro 0.30, Azure Linux 0.39. The Rust stage moved about 0.7 min into the Alpine build and 1.1 into the determinism check.
Downloads: every consumer downloaded the whole 140 MB guest-artifacts artifact, in p50 5 s on Linux and 7 s on Windows (p90 9–11 s). Per component, in ci: build the guest-artifact components in parallel and download only what each job uses #711's run 39624800851: kernel 7.5 MB, Alpine 8.2, Ubuntu initramfs 38.5, Ubuntu distro layer 43.0, Azure Linux 42.5, scratch template 0.07, debug kernel 7.5. Each download step costs about 1–3 s, so the VMM tests download theirs in 2–6 s, and the jobs that read every image take about 5–10 s more than before.
Of the 145 successful first-attempt runs of that period, 137 ran the OpenVMM VMM tests, 119 of them with cold guest artifacts. The WHP VMM tests started a median 6.0 min after the run started on cold runs, against 0.8 on warm ones, and finished last in 74 of the 119 cold runs; the WHP platform job, behind its benchmark runner, finished last in 33. Examples: cold push 39603556404 (artifacts done at 5.9 min, WHP VMM tests 5.9–26.8 min, run 27.0 min), warm push 39596666239 (0.7–21.9, run 22.0), cold pull request 39602304045 (5.7–28.1, run 28.2).
I replayed the 172 artifacts jobs of that period whose commits are available: each run's keys are recomputed from the tree it tested (the pushed commit, or the pull request's head merged into dev at the time), and GitHub's cache scopes are simulated (dev plus the run's own pull request; a job saves what it built when it finishes). Against the observed misses, the replay predicts 166 of 172 Alpine outcomes and 171 of 172 kernel outcomes.
¹ Approximated by dropping the host-only nvx-rs files from the keys: src/host/**, src/testing/**, src/bin/nvx-rs.rs, src/bin/nvx-fake-openvmm.rs, tests/**, README.md, LICENSE, and artifacts.json. An exact set, such as Cargo's dependency information, may also drop #[cfg(test)] modules.
#699 has since merged as #710, on 2026-10-21 at 19:27 UTC. Its keys also leave out the test modules (the tests.rs files, the tests directories, and the managed agent's loopback harness), so it should remove at least the misses estimated here; #702 is #713, still open.
Cold builds caused only by host-only nvx-rs changes: 37 of 137 (27%).
What both leave (89 Alpine misses): 62 pull-request runs whose own changes touch the guest binaries or other guest inputs, 16 pushes whose pull request's artifacts were stale or expired, and 11 pull-request runs that started before dev saved its new keys. Under ci: key the guest images and OpenVMM VMM tests on the guest binaries' sources, not all of nvx-rs #699, the first group still runs the VMM tests.
Decision: split into two producers
To see whether the remaining misses matter for the tail, I replayed the 133 successful first-attempt runs of that period: every job keeps its measured queue time and duration, and starts when the jobs it waits for finish. The model reproduces the observed run times within 0.12 min (median).
Scenario
All runs, p50 / p90 / p99 (min)
27 runs in which no self-hosted job queued 2 min or more, p50 / p90 / p99
Today, one producer
29.7 / 48.7 / 93.7
26.4 / 27.5 / 28.2
Today, kernel + Alpine beside Ubuntu + Azure Linux
Two producers are enough: one per component gains nothing more, because the Ubuntu chain, mostly its 2.9-minute determinism check, bounds the jobs that read every image either way. A kernel + Alpine job with a warm kernel should take about 1.3 min.
So ci: build the guest-artifact components in parallel and download only what each job uses #711 builds the kernel and the Alpine initramfs in artifacts and the Ubuntu and Azure Linux images in a new distro-artifacts job beside it, uploads one artifact per component, and makes each job download only what it reads. The VMM tests and the debug-kernel jobs wait only for artifacts. The debug-kernel lane, the determinism check, the provenance, and the keys are unchanged.
At first, #711's runs hit the cache, because #710's dev push had just built and saved every new key. So, on a temporary branch, I ran ci.yml's two producers as #711 defines them, once on GitHub-hosted runners:
without their change-detection dependency;
with save-cache: false;
with a comment line added to .dockerignore, which every guest key hashes, so that every component missed.
The experiment is run 39665813966. The baseline is the single job of #710's dev push 39661815501, which built every component cold under the same keys an hour earlier:
Job, all components cold
Kernel
Alpine
Ubuntu initramfs
Determinism
Distro layer
Azure Linux
Uploads
Job wall time
Before: one artifacts job
5.6 min
0.7
0.6
2.9
0.3
0.4
0.1
10.9 min
After: artifacts
6.1
0.8
0.05
7.0 min
After: distro-artifacts
1.3¹
3.0
0.3
0.4
0.1
5.2 min
¹ Including the base and guest-binary Docker stages, which distro-artifacts builds again: 0.6 min more.
Both jobs started 5 s after the run was created. The six component artifacts held 7.5 MB (kernel), 8.2 (Alpine), 38.5 (Ubuntu initramfs), 43.0 (distro layer), 0.07 (scratch template), and 42.5 (Azure Linux).
With a cold kernel, every guest consumer can start after about 7.0 min instead of 10.9; the kernel build alone varies between about 5.3 and 6.1 min.
The split costs about 1.3 more GitHub-hosted minutes per cold run (12.2 against 10.9 here), and nothing on a warm one.
The experiment saved no cache, and its branch is deleted.
#711's own CI, every image cold.#652 changed the guest binaries' sources, and #711, rebased onto it, ran before any dev push had saved the new keys. In its run 39690060426 of the head 46da18e49, every guest image missed the cache, while the kernel and the debug kernel hit:
Job, images cold
Kernel
Alpine
Ubuntu initramfs
Determinism
Distro layer
Azure Linux
Job wall time
artifacts
hit
0.9 min
1.0 min
distro-artifacts
1.4²
3.0
0.3
0.4
5.3 min
² Including the base and guest-binary Docker stages, as in the experiment.
Both producers started 0.4 min after the run was created.
The three OpenVMM VMM test jobs started at 1.6 min, and the KVM debug-kernel job finished at 5.2 min, before distro-artifacts (5.7 min). With the single job, they would have waited for every image: a median 5.3 min when it missed, above.
The microVM test and platform jobs started when distro-artifacts finished.
After #711 merges, the timing with queue time of at least five comparable runs with cold guest images, before and after, belongs here; #711 lists it under its post-merge verification.
Raw per-run data: the 200 artifacts jobs (build seconds per component; empty when the cache hit)
Activity
ppenna commentedon Oct 21, 2026
Per-component measurements, estimates for #699 and #702, and the split decision
Measured on 2026-10-21 for #711. Data: the jobs API of the 449 CI runs created from 2026-10-13 to 2026-10-21 13:55 UTC, and the logs of the 202
artifactsanddebug-kerneljobs that ran longer than 45 s since 2026-10-15. The jobs API does not list a composite action's steps, so the per-component times come from the step groups in the job logs.The current guest build, with the Rust guest-binary Docker stage and the
-v2image keys, started with #561, merged 2026-10-19 10:52 UTC (#561's own pull-request runs, from 2026-10-16, use it too). The numbers below cover the 200artifactsjobs since then: 47devpushes and 153 pull-request runs.Cache misses and build time by component
build-guest-binaries23), which BuildKit runs beside the base stage; Alpine initramfs 4.5 s; Ubuntu initramfs 34 s; Ubuntu determinism 165 s; distro layer 15 s; Azure Linux initramfs 19 s; kernel 323 s.-v1keys): kernel 5.6 min, Alpine 0.13, Ubuntu 0.63, determinism 1.77, distro 0.30, Azure Linux 0.39. The Rust stage moved about 0.7 min into the Alpine build and 1.1 into the determinism check.guest-artifactsartifact, in p50 5 s on Linux and 7 s on Windows (p90 9–11 s). Per component, in ci: build the guest-artifact components in parallel and download only what each job uses #711's run 39624800851: kernel 7.5 MB, Alpine 8.2, Ubuntu initramfs 38.5, Ubuntu distro layer 43.0, Azure Linux 42.5, scratch template 0.07, debug kernel 7.5. Each download step costs about 1–3 s, so the VMM tests download theirs in 2–6 s, and the jobs that read every image take about 5–10 s more than before.Effect on the run
Of the 145 successful first-attempt runs of that period, 137 ran the OpenVMM VMM tests, 119 of them with cold guest artifacts. The WHP VMM tests started a median 6.0 min after the run started on cold runs, against 0.8 on warm ones, and finished last in 74 of the 119 cold runs; the WHP platform job, behind its benchmark runner, finished last in 33. Examples: cold push 39603556404 (artifacts done at 5.9 min, WHP VMM tests 5.9–26.8 min, run 27.0 min), warm push 39596666239 (0.7–21.9, run 22.0), cold pull request 39602304045 (5.7–28.1, run 28.2).
What #699 and #702 remove
I replayed the 172
artifactsjobs of that period whose commits are available: each run's keys are recomputed from the tree it tested (the pushed commit, or the pull request's head merged intodevat the time), and GitHub's cache scopes are simulated (devplus the run's own pull request; a job saves what it built when it finishes). Against the observed misses, the replay predicts 166 of 172 Alpine outcomes and 171 of 172 kernel outcomes.devpushes (of 47)¹ Approximated by dropping the host-only
nvx-rsfiles from the keys:src/host/**,src/testing/**,src/bin/nvx-rs.rs,src/bin/nvx-fake-openvmm.rs,tests/**,README.md,LICENSE, andartifacts.json. An exact set, such as Cargo's dependency information, may also drop#[cfg(test)]modules.#699 has since merged as #710, on 2026-10-21 at 19:27 UTC. Its keys also leave out the test modules (the
tests.rsfiles, thetestsdirectories, and the managed agent'sloopbackharness), so it should remove at least the misses estimated here; #702 is #713, still open.nvx-rschanges: 37 of 137 (27%).devpushes that rebuild what their pull request built: 10 of 36 cold pushes (28%). Most of the others merged after another merge changednvx-rs, so their pull request's last run had other keys. With ci: key the guest images and OpenVMM VMM tests on the guest binaries' sources, not all of nvx-rs #699, 9 of 27. ci: reuse a pull request's guest images on its dev push #702 should also save the reused images to thedevcache: otherwise later pull-request runs lose thedev-scope hits that the push's build gives them today.devsaved its new keys. Under ci: key the guest images and OpenVMM VMM tests on the guest binaries' sources, not all of nvx-rs #699, the first group still runs the VMM tests.Decision: split into two producers
To see whether the remaining misses matter for the tail, I replayed the 133 successful first-attempt runs of that period: every job keeps its measured queue time and duration, and starts when the jobs it waits for finish. The model reproduces the observed run times within 0.12 min (median).
artifactsand the Ubuntu and Azure Linux images in a newdistro-artifactsjob beside it, uploads one artifact per component, and makes each job download only what it reads. The VMM tests and the debug-kernel jobs wait only forartifacts. The debug-kernel lane, the determinism check, the provenance, and the keys are unchanged.Cold path after the split
At first, #711's runs hit the cache, because #710's
devpush had just built and saved every new key. So, on a temporary branch, I ran ci.yml's two producers as #711 defines them, once on GitHub-hosted runners:save-cache: false;.dockerignore, which every guest key hashes, so that every component missed.The experiment is run 39665813966. The baseline is the single job of #710's
devpush 39661815501, which built every component cold under the same keys an hour earlier:artifactsjobartifactsdistro-artifacts¹ Including the base and guest-binary Docker stages, which
distro-artifactsbuilds again: 0.6 min more.artifactstakes about 1.0 min, as in ci: build the guest-artifact components in parallel and download only what each job uses #711's run below. So the VMM tests and the debug-kernel jobs start about 4 min earlier, while the microVM test and platform jobs wait fordistro-artifactsabout as long as for the single job.#711's own CI, every image cold. #652 changed the guest binaries' sources, and #711, rebased onto it, ran before any
devpush had saved the new keys. In its run 39690060426 of the head46da18e49, every guest image missed the cache, while the kernel and the debug kernel hit:artifactsdistro-artifacts² Including the base and guest-binary Docker stages, as in the experiment.
Both producers started 0.4 min after the run was created.
distro-artifacts(5.7 min). With the single job, they would have waited for every image: a median 5.3 min when it missed, above.distro-artifactsfinished.After #711 merges, the timing with queue time of at least five comparable runs with cold guest images, before and after, belongs here; #711 lists it under its post-merge verification.
Raw per-run data: the 200
artifactsjobs (build seconds per component; empty when the cache hit)