Repository navigation
Conversation
4e656ec to
062b745
Compare
potiuk
left a comment
There was a problem hiding this comment.
Nice idea — caching the third-party layer on the manifests alone should save a lot of CI image rebuild time. We actually had a similar mechanism implemented before we switched to uv, so this is bringing back something that worked well for us. One thing needs fixing before it can go in, plus a few smaller suggestions.
Needs fixing: the pre-sync has no fallback
The new uv sync --frozen ... --no-install-workspace step hard-fails whenever the lock does not match the workspace, while the full sync after it (install_from_sources) falls back to re-resolving without --frozen (AIRFLOW_FALLBACK_NO_CONSTRAINTS_INSTALLATION=true by default). I reproduced this with the exact command from the PR against a manifest-only tree built from this branch (--dry-run):
- lock in sync: OK
- new workspace member added without regenerating
uv.lock:error: The lockfile at uv.lock needs to be updated, but --frozen was provided: Missing workspace member ... - dependency added to an existing member without regenerating
uv.lock: OK (stale set installed, the full sync then fixes it up)
So a PR that adds a new provider/distribution and doesn't refresh uv.lock used to get an image via the fallback, and now the image build aborts at the pre-sync step. Since the pre-sync is purely a cache warm-up and the full sync covers everything anyway, it should never fail the build — e.g. let it fail soft:
uv sync ... || echo "Pre-sync of locked dependencies failed - the full sync below will install them."The comment above the step ("retains its existing resolution fallback") should then describe that the pre-sync is best-effort, since as written it suggests the stale-lock path still degrades gracefully.
Suggestions
- The manifest stage treats
uv.lockas optional (if [ -f uv.lock ]), but the pre-sync always requires it (Unable to find lockfile at uv.lock, but --frozen was provided). With the fail-soft change above that becomes harmless; otherwise either drop the guard or skip the pre-sync when there is no lock. - The pre-sync duplicates the
uv syncflags frominstall_from_sourcesinscripts/docker/install_airflow_when_building_images.sh(--group ci-image, thelxml/xmlsec--no-binary-packageworkaround, …). If they drift, the two layers install different builds of the same packages and the full sync has to reinstall them. Consider adding a "locked third-party only" mode toinstall_from_sources(e.g. an env var that adds--no-install-workspace) and calling the script instead. - Optional: the cache key is the full content of every
pyproject.toml. On main over the last 30 days, 66 of 994 commits touched a manifest oruv.lock, and 20 of those changed onlypyproject.tomlfiles — so the cache hit rate is already good, but keying onuv.lockplus the dependency sections only would avoid a few more rebuilds. - Optional:
UPGRADE_RANDOM_INDICATOR_STRINGnow feeds the pre-syncRUN, so every upgrade-to-newer-dependencies build re-executes it only to skip the install.
Drafted-by: Claude Code (Opus 5.5); reviewed by @potiuk before posting
A frozen sync fails when uv.lock is missing or lacks a workspace member, which would fail the whole image build even though the full sync after COPY already re-resolves in that case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for the reviews. Here is what changed:
How I tested the pre-sync step. I ran the
In the real CI image build on eb931fa (job), the frozen dry run reported "Would install 801 packages" in 0.6 s, and the pre-sync then installed all 801 locked third-party packages, in a 34.2 s step. After The install script is inlined into The 13 checks that failed earlier came from the Oct 14 base: CI on eb931fa: green, with 132 checks passed and none failing. While this was open, main briefly broke its own asset-permission tests; #75126 and #75138 fixed them, and merging main in picked up those fixes. |
The pre-install repeated the uv sync flags of install_from_sources, so the two layers could drift apart and install different builds of the same packages. INSTALL_LOCKED_THIRD_PARTY_ONLY makes the install script add --no-install-workspace, skip the re-resolving fallback and skip the steps that need workspace packages, so both layers run the same uv sync. The Dockerfile.ci step stays best-effort and is still skipped for upgrade builds.
There was a problem hiding this comment.
Correct and verified on the current head's CI image build (801 locked packages pre-installed, then only 141 workspace packages, pip check clean); potiuk's must-fix and suggestion 2 are addressed, and what remains is a stale PR description plus two optional nits.
Smaller observations
general— The PR description is stale after 4bb265f. It still says that installation errors fail the build, but the pre-install now ends in|| echo "Skipping the locked dependency pre-install...", so every pre-install failure is swallowed and the (empty or partial) layer is committed and can be exported to the registry cache. The Validation section also quotes the pre-rebase run (54.8 s, 791 packages); the current head's CI image build shows 801 packages in 29.6 s and then 141 workspace packages in 13.4 s. Please refresh the description from your 2026-10-18 comment so that it matches the code that will be merged.- See inline comments on
Dockerfile.ci:1985,scripts/docker/install_airflow_when_building_images.sh:49.
@potiuk WDYT?
This review was drafted by an AI-assisted tool and
confirmed by an Apache Airflow maintainer. The findings
below are observations, not blockers; an Apache Airflow
maintainer — a real person — will take the next look at the
PR. If you think a finding is mis-applied, please reply on
the PR and a maintainer will weigh in.More on how Apache Airflow handles maintainer review:
Contributing guide.
A frozen dry run now decides whether to skip: it fails when uv.lock is missing or lacks a workspace member, and only then is the warm-up skipped. Any other failure, such as a network error or an lxml/xmlsec build error, would fail the full sync as well, so it now fails the build instead of leaving an empty layer in the cache. The install script also refuses the locked mode for upgrade builds itself instead of relying on the caller.
Source-only changes currently invalidate CI image dependency installation. Extract workspace
pyproject.tomlfiles anduv.lockinto a separate build stage and preinstall locked third-party dependencies before copying the full checkout. The existing full sync still installs workspace packages and keeps its resolution and validation path.Split from #74173 at reviewer request so this Dockerfile optimization can be reviewed and reverted independently from the PR-only snapshot transport.
How the preinstall works:
install_airflow_when_building_images.shwithINSTALL_LOCKED_THIRD_PARTY_ONLY=true. That mode adds--no-install-workspaceto the same frozenuv syncthat the full installation runs, so the two layers cannot drift apart.uv.lockdoes not match the manifests (a workspace member missing from the lock, or no lock at all), the warm-up is skipped and the full sync afterCOPYre-resolves as before.lxml/xmlsecbuild error, fails the build, because it would fail the full sync too. A partial dependency layer therefore cannot be published through the registry'smode=maxcache.Dockerfile.ciand in the script itself.Validation:
COPY ., the full sync installed only the 141 workspace packages in 15.8 s.Dockerfile.cipreinstall step in the CI image against a manifest-only tree (everypyproject.tomlplusuv.lock):uv sync.COPYandRUNcached, while changing the rootpyproject.tomloruv.lockinvalidates both. This checks the cache boundary, not full-image equivalence or registry performance.maincommits touched a manifest oruv.lock, so most commits would reuse the layer.Actual reuse and net savings can only be measured on a source-only build after
mainpublishes this layer to the registry.Was generative AI tooling used to co-author this PR?
Generated-by: Codex (GPT-6) and Claude Code (Opus 5.5), following the Airflow guidelines.