perf(docker): add sccache fallback to the builder stage - #81
Open
kkroo wants to merge 2 commits into
Open
Conversation
The three cargo steps in the builder stage already use BuildKit `--mount=type=cache` for the cargo registry and /build/target, so on a warm daemon this changes nothing. Those mounts are daemon-local, though, and the parent repo (Blockcast/pim-multicast-gateway) builds this image against two persistent BuildKit daemons holding separate RWO caches — a random pick cold-missed ~50% of builds, which is why its docker-build.yml pins a daemon per service. When that pin lands on the peer daemon, or the mount is evicted, the whole workspace recompiles with no shared fallback. sccache is keyed on the compilation itself and stores objects in the cluster's ceph RGW, so it covers exactly those builds. This is tail-risk insurance, not a steady-state speedup: 0 on a warm daemon. Mirrors packages/dual-stack-relay/Dockerfile:17-33 in the parent repo. Existing cache mounts are kept — sccache layers under them, it does not replace them. The pinned rust:1.96 base is unchanged. The two helper scripts are vendored under .github/scripts/ because the parent builds this image with `context: moq-rs`, which puts its own copies outside this build context. Same pattern hang-mmt-fec and libmmt already use for their setup-rust-sccache copies. Each vendored file carries a provenance header and a one-line drift check; apart from that header both are byte-identical to the parent's. All three cargo steps share the builder stage and its single /build/target mount, so sccache-env.sh's per-stage verdict file is load-bearing here: it stops a mid-stage probe flap from changing RUSTC_WRAPPER between steps and discarding the dependency layer. No workflow change is required — the parent's docker-buildx-with-session-retry.sh:83-99 already appends SCCACHE_RGW_ENDPOINT and the two ceph_s3_* BuildKit secrets to every buildx invocation. This repo's own `docker build` in pr.yml and heap-profile-image.yml passes none of them, so it fails open and behaves exactly as before. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: a10285a
Critical Issues (0)
Important Issues (0)
Suggestions (0)
Strengths
- The cache setup fails open for local and uncredentialed builds while keeping BuildKit secrets out of image layers.
- The per-stage verdict avoids changing
RUSTC_WRAPPERacross cargo steps sharing/build/target. - The vendored scripts match the parent repository's source-of-truth implementation after the documented provenance header.
Recommended Action
- No blocking changes requested.
…tes to errors setup-rust-toolchain pins nothing, so the pr workflow now runs clippy 1.98 with -D warnings and fails on two lints main never saw: - needless_late_init in SubgroupsWriter::append: bind (group_id, subgroup_id) from the branch expression instead of declaring them first. - result_unit_err on Queue::push_and_wait_until_popped: return a typed QueueClosed error (thiserror, already a dependency) instead of Err(()). The one caller only .ok()s the result, so behavior is unchanged. Verified locally with rustc 1.98.0: cargo clippy --no-deps under RUSTFLAGS=-D warnings is clean and cargo fmt --check passes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: df6de2a
Scope note: the Dockerfile and the two vendored sccache-*.sh scripts are byte-identical to the tree already reviewed clean at a10285ad; the only new material at this head is the clippy commit touching moq-transport. Both prior-head sections were zero, so there are no prior findings to disposition.
Critical Issues (0)
Important Issues (0)
Suggestions (2)
- [types/errors]
moq-transport/src/watch/queue.rs:86—QueueClosedis a unit struct, so the "closed before push" arm at line 93 drops the caller's item with no way to recover it. SiblingQueue::push(line 20) deliberately hands it back asErr(item). Since you are designing this error type now, it may be worthQueueClosed<T>(pub T)on that arm — though it costs theCopy/Eqderives and the current sole caller does not need it, so declining is entirely reasonable. - [build/pre-existing]
Dockerfile:7— this commit exists because CI compiles on floating stable (1.98) while the shipped image pinsrust:1.96-bookworm, so the release artifact and the lint gate use different compilers and this class of break recurs on every stable bump. Out of scope here, but arust-toolchain.toml(or atoolchain:input onsetup-rust-toolchain) would make it a deliberate bump rather than a surprise.
Strengths
- Verified independently rather than taken on faith:
push_and_wait_until_poppedhas exactly one caller (session/publisher.rs:900) and it.ok()s the result, so the signature change is genuinely behavior-preserving as the commit message claims. - The
result_unit_errfix is complete, not partial. The two remainingResult<(), ()>signatures (session/publisher.rs:888,session/subscriber.rs:178) arepub(super)and private respectively, so the lint does not reach them — no follow-up breakage queued behind this. subgroup.rs:117binds both ids from one branch expression with all three fieldsu64, so the0literal infers correctly and the two arms are value-for-value identical to the old assignments.thiserrorwas already a direct dependency ofmoq-transport, socargo machetestays clean and no new supply-chain surface is added to clear a lint.- Commit message states the exact toolchain used for local verification and names both lints, which is what made the above checks cheap to reproduce.
Recommended Action
- No blocking changes requested.
build,reuse, andheap-profile-imagewere stillqueuedat review time — this approval attests to the diff as read, so merge on green.- Consider the two suggestions opportunistically; neither gates this PR.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Wires sccache into the three cargo steps in the
builderstage(
Dockerfile:7): the dependency build, the real-source build, and theprofiling-variant build (which contains both
cargo rustcandcargo build).All three were previously unwired — this Dockerfile and
FFmpeg/Dockerfilewere the only hot-path Rust images in the pim-multicast-gateway build graph with
no sccache at all.
Why — and what this is not
This is tail-risk insurance, not a steady-state speedup. The expected win on a
warm daemon is zero.
All three cargo steps already have BuildKit cache mounts (
--mount=type=cachefor
/usr/local/cargo/registryand/build/target), and those keep the commoncase fast. The gap is that those mounts are daemon-local, and the parent
repo (
Blockcast/pim-multicast-gateway) builds this image against twopersistent BuildKit daemons holding separate RWO caches. From its
.github/workflows/docker-build.yml:When that pin lands on the peer daemon, or the mount is evicted, the whole
workspace recompiles with no shared fallback. sccache is keyed on the
compilation itself and stores objects in the cluster's ceph RGW, so it covers
exactly those builds — roughly 5–15 slot-minutes recovered when it fires, and
nothing otherwise.
How
Mirrors the reference implementation at
packages/dual-stack-relay/Dockerfile:17-33in the parent repo. No newmechanism is introduced.
fallback; it does not replace them.
rust:1.96-bookwormis unchanged.--mount=type=secretforceph_s3_key/ceph_s3_secret. Nothing ishardcoded, and secrets never enter a layer or a cache key.
Why the scripts are vendored here. The parent builds this image with
context: moq-rs(its build-images matrix entry formoq-pub-mmtp), so theparent's
.github/scripts/sits outside this build context and cannot beCOPY'd. Vendoring under.github/scripts/is the same patternhang-mmt-fecand
libmmtalready use for theirsetup-rust-sccachecopies. Each vendoredfile carries a provenance header naming the source of truth plus a one-line
drift check; apart from that header both are byte-identical to the parent's,
verified by running the documented command:
Why the per-stage verdict file matters here specifically. Unlike FFmpeg's
builder — where each crate has its own
target/mount — all three cargo stepshere share the
builderstage and its single/build/targetmount.RUSTC_WRAPPERparticipates in cargo's fingerprint, so a mid-stage probe flapthat enabled sccache for the dependency build and then disabled it for the
source build would not merely lose the cache, it would discard the dependency
layer and recompile it — strictly worse than never enabling sccache.
sccache-env.sh's verdict file makes the first decision in the stage bind therest of it, which is exactly the case it was written for.
No workflow change is required.
.github/scripts/docker-buildx-with-session-retry.sh:83-99in the parent repoalready appends
--build-arg SCCACHE_RGW_ENDPOINTand both--secretflags toevery buildx invocation in the
build-imagesjob.Verification
Positive control (the instrument works before trusting a negative):
grep -c -i sccacheon the parent'spackages/dual-stack-relay/Dockerfile→15, while the same grep on this Dockerfile returned 0. The absence was
real, not a tooling artifact.
Coverage audit — every
RUNblock containing a cargo invocation now sourcessccache-env.shand carries both secret mounts (3/3 covered; the profilingblock's
cargo rustcandcargo buildare both inside the coveredRUN).Lint —
docker buildx build --check: clean apart from one pre-existingFromAsCasingwarning atFROM debian:bookworm-slim as moq-pub, confirmedpresent on
origin/mainand deliberately left alone rather than fixed as adrive-by.
No
# syntax=directive here — this Dockerfile uses the built-in frontend.Verified
RUN --mount=type=secretworks there with a real build (this filealready uses
--mount=type=cacheunder the same frontend, so it is parity, nota new requirement).
Fail-open, verified by real builds on
rust:1.96-bookwormwith the exactpreamble and
RUNshape from this diff. All exit 0 and compile:pr.yml/heap-profile-image.ymlsccache disabled (no SCCACHE_RGW_ENDPOINT build-arg)sccache disabled (RGW credentials not mounted)sccache disabled (RGW endpoint probe failed: ...)That first row is the important one for this repo:
pr.yml'sdocker build --file Dockerfile ... .andheap-profile-image.ymlpass none ofthese build-args or secrets, so their behaviour is byte-for-byte unchanged.
Expected one-time cost
The
RUNcommand strings changed, so their layer cache keys change. The firstbuild after this merges rebuilds those layers once. Secret mounts themselves are
excluded from cache keys, so there is no recurring cost.
Required follow-up
The parent repo's
moq-rssubmodule pointer must be bumped to include thiscommit before it takes effect. Deliberately not done here — that is a
separate PR in
Blockcast/pim-multicast-gatewayonce this merges.Companion
Blockcast/FFmpegcloudflare#149 — same change forFFmpeg/Dockerfile, the other unwiredhot-path Rust image.
🤖 Generated with Claude Code