Optimize preview builds: phased plan for the fastest PR previews (tracking)
Nobody has claimed this yet.
- Dominant language
- Shell
- Stars
- 0
- Forks
- 1
- Avg merge
- 32m
- Merged PRs (30d)
- 3
Description
Summary
Umbrella tracker for making PR preview builds fast across the lecture repos, from a measured, adversarially-verified July 2026 analysis. Mechanisms, savings arithmetic, rejected alternatives (single-page builds, Sphinx -j, CPU migration of the GPU repo, conda-cache-instead-of-containers, and more) and open questions live in PROJECT-OPTIMIZE-PREVIEWS.md; this checklist, not the doc, is the status tracker.
Headline finding: preview time goes to fixed overhead, not to building the author's change. The 40-minute cold builds were a silent cache-freshness failure (#83), not inherent cost — speed work and the #83 alerting fix are one project.
Measured baseline
lecture-dpPR, warm cache — ~2.7 min: container pull 129s (75% of the run), HTML build 22s, cache restore 3s.lecture-dpPR, cold cache — ~40 min: notebook re-execution 2,350s; hit every PR for ~2 months while the weekly cache builder failed silently (#83).lecture-python.mystPR, every time — ~17 min: PDF 379s + notebooks 117s (formats unused in previews), conda 102s + JAX 55s (bakeable into the custom AMI), checkout 101s, HTML 80s, deploy 61s.- Lean image pull: 2,741 MB compressed, 1,233 MB of it TeXLive — unused by an HTML-only preview.
Expected results (today → Phase 1 → end state)
lecture-dpsmall edit: ~2.7 min → ~2.2–2.5 min → ~1.5–2.5 min incl. deploy, links at ~20s.lecture-dpcold miss: ~40 min silent → ~10× rarer, loud, once per PR → ~18–28 min, rare.lecture-python.mystPR: ~17 min → ~8–9 min → ~3.5–5 min (prose ~2.5–3.5).- Second push, same PR: same as first → execution skipped when code unchanged → ~1.5–2 / ~3.5–5 min.
Caveat on the lecture-dp rows: its ci.yml has no preview deploy step today — it builds HTML and uploads an artifact — so "incl. deploy" is for a preview still to be stood up.
Phase 1 — days, all S-effort, independently shippable
Critical path is the first item: it alone outweighs every actions-repo item here combined, and is the only one gated on a decision (Decision 1) rather than effort.
-
lecture-python.myst: HTML-only PR previews; three-format build behind afull-previewlabel; PDF/notebook coverage moves to weekly cache.yml (−7–7.8 min/PR). Not started —.github/workflows/ci.ymlstill runs the notebook build (--custom-builder=jupyter,:72) and pdflatex (:84) on every PR, ungated. → #148 (gated on #146) -
#83 alerting fix:— done in v0.11.0 (#122, #127) by another mechanism:curlREST fallback +::error::guard increate-failure-issue.sh;upload-failure-reports: trueinsidebuild-jupyter-cachebuild-jupyter-cache/scripts/create-failure-issue.shwas deleted (a5da9f3) rather than hardened, and filing moved toactions/github-script(build-jupyter-cache/action.yml:385), sidestepping thegithub.action_path/no-gh/label-validation trio that made the old path fail silently in container jobs.upload-failure-reportsshipped as an input defaultingtrue(build-jupyter-cache/action.yml:42-51), passed to all three innerbuild-lecturescalls (:168,:178,:188). #127 covered the abort-before-builds case. -
lecture-dp:save-cache: 'true'on the restore step (one line). Itsci.ymlstill callsquantecon/actions/restore-jupyter-cache@v0without it. The input exists since v0.5.2 (#24), the save-mode outputs/fail-on-miss bugs were fixed by #104 in v0.9.0, and the path is CI-covered since v0.10.0 (.github/workflows/test-actions.yml:270,:395) — nothing blocks it. → #149 -
lecture-dp: cache-on-merge job on push to main (paths-ignore: [environment.yml]; weekly rebuild stays the anchor; no actions-repo change). Not started — itscache.ymltriggers on the weekly cron pluspush: paths: [environment.yml], the inverse of what this needs. → #149 -
--depth=1on the two still-unbounded SHA fetches inscripts/detect-changed-lectures.sh:26-27— this repo, shippable today.lecture-python.mysthas no copy of the script (it callspreview-netlify@v0, which invokes ours), so only the checkout side is consumer work. → #147 -
fetch-depth: 1on the consumer checkouts (lecture-python.mystci.yml:21,lecture-dpci.yml:19, both stillfetch-depth: 0); −80–95s againstlecture-python.myst, whose checkout measured 101s. Land with the item above — a shallow checkout without bounded fetches makes them worse. → #148 / #149 (with #147) - zstd layers for
quantecon-buildinbuild-containers.yml(−20–35s per PR pull); add a tag input totest-container.ymland validate a:zstdtag first. Neitherdocker/build-push-actioncall setsoutputs:(.github/workflows/build-containers.yml:66-74,:108-116), so both push default gzip;test-container.yml'sworkflow_dispatch:takes noinputs:(:6-11) and:latestis hardcoded at:33,:99. → #150 - Concurrency cancellation: fix
templates/ci.yml:24-26togroup: ci-${{ github.event.pull_request.number || github.run_id }}(it has the group andcancel-in-progress: true, not the fallback), then add the block to the consumerci.ymlfiles, which have none today. The fallback is load-bearing: both consumers also trigger onworkflow_dispatch, so a verbatim copy puts every manual run in the empty groupci-, cancelling each other. Also closes a stale-overwrite hazard. → #147 - Paths filters (
paths-ignorefail-open variant) so docs-only PRs skip the preview —templates/ci.ymlhas nopathskey at all; plus gate both preview actions' CLI installs onpull_request: they gate on the trust check alone (preview-netlify/action.yml:56-59,preview-cloudflare/action.yml:68-71) while the deploys they feed (:63,:75) also requiregithub.event_name == 'pull_request', so aworkflow_dispatchrun burns the ~25–45s install then skips the deploy. Same two steps #105 wants pinned — one PR, not two. → #147 (CLI-install gate: #105)
The --depth=1, || github.run_id and paths-ignore changes are one small actions-repo PR; the CLI-install gate folds into #105; the zstd/tag-input item stays separate because :zstd must be validated before :latest moves. Everything else is consumer-repo work, none started.
Phase 2 — 1–2 weeks: slim image, AMI bake, instant first signal
- Ship
containers/quantecon-preview(~1.7 GB, zstd; drop the TeX layer; drop thejupytermetapackage — it transitively reinstalls jupyterlab; keep MKL/ipykernel for execution parity; bake pinned netlify-cli); switchlecture-dpci/cache/publish to it; add a stack-pins-match assertion totest-container.yml. Not started — there is nocontainers/quantecon-preview. → #151 - Bake miniconda + env + pinned
jax[cuda13]/numpyro + netlify-cli into the RunsOn AMI (the GPU repo stays on RunsOn; CPU migration evaluated and rejected) with anenv_hashdrift guard; flip ci.yml tosetup-environment's container-mode path (verify the AMI marker file exists first; staged rollout viaworkflow_dispatch). → #152 - Instant placeholder PR comment (no container, no checkout — REST changed-files) with deterministic
pr-Nlinks; both actions comment only after a successful deploy (preview-netlify/action.yml:101,preview-cloudflare/action.yml:148). #14 has left this bundle (Cloudflare stable-alias fix, v0.11.1, #131); the other half — the shared comment-script extraction, PLAN item 6 — still stands and has grown: the twogithub-scriptbodies (preview-netlify/action.yml:111-186vspreview-cloudflare/action.yml:159-242) now differ by ~12 lines (adeploymentUrlconst, two title lines, two marker lines, a 7-line "This deployment" block) against item 6's recorded "only 2 lines differ". → #153 - Cache freshness warning:
.cache-metadata.jsonat save; age check +::warning::/step-summary inrestore-jupyter-cache;cache-age-daysoutput. Not started — the action declares exactly two outputs,cache-hitandcache-key(restore-jupyter-cache/action.yml:39-45), and nocache-metadata/cache-agestring exists in the repo. No consumer repin on release: all 11 call sites float on@v0. → #154
Phase 3 — cold-case compression, incrementality, unification
- Parallel notebook pre-execution (
jcache project execute --executor local-parallel) for container repos — cold 2,350s → ~950–1,550s; pilot for OOM (16 GB, no worker knob); first step of #2 → #155 - True Sphinx incrementality via mtime manipulation keyed to the cache-build SHA (touch the PR diff; full rebuild on deletions/renames/
_toc.yml/_config.yml) → #156 - RunsOn Magic Cache + migrate pymyst's weekly artifact chain onto
build-jupyter-cache/restore-jupyter-cache(flip cache.yml and ci.yml together; move jax/numpyro intoenvironment.ymlpip section first); ticks the PLAN.md actions/cache-on-RunsOn blocker → #157 - Cost right-sizing:
g4dn.xlarge+ spot for pymyst PR previews only (A/B a full cold run in 16 GB first) → #158
Decisions needed
Unanswered since July; the first gates the largest Phase 1 win.
- HTML-only previews: should PDF/ipynb download buttons show week-old assets with a stale note, or be hidden? Is weekly detection of LaTeX breakage (with #83 alerting now working) an acceptable SLA? → #146
- Netlify vs Cloudflare for the QuantEcon/meta#327 rollout — with CLIs pre-baked they tie on speed; the decider is Cloudflare's 25 MiB per-asset / 20k-file caps vs
_pdfsizes. → Settled: Cloudflare Workers static assets (QuantEcon/infrastructure#4, #145); the asset caps move to the #145 pilot checklist. - Pilot gates: does 16 GB survive 4 concurrent kernels on the heaviest DP lectures; does the deployed GPU AMI contain
/etc/quantecon-container? → carried in #155 and #152
Related
- PLAN.md — this plan covered its P0 (#83, done) and items 5 (done — the Cloudflare alias fix, v0.11.1), 6 (open — folded into the Phase-2 placeholder-comment line) and 9 (done).
- Prerequisites cleared: #83 (v0.11.0), #104 (v0.9.0 — the save-mode fix the
lecture-dpone-liner depends on), #14 (v0.11.1). - #18 (container-mode caching) and #2 (isolated per-lecture execution) — Phase 2 pre-baking and Phase 3 parallel execution advance both.
- #105 — overlaps Phase 1's CLI-install gate; land as one PR.
- #135 — release gating. Action changes reach consumers the moment
v0moves, since every call site floats, so the cache-age warning and thetemplates/ci.ymlchanges would ship unverified until that gate exists. Containers are looser still:quantecon-previewand the zstd images reach consumers through:latest, which no release gates — hence the:zstd-tag and stack-pins-match validations above. - QuantEcon/meta#327 — preview unification rollout this design feeds.
Tracker structure
Converted to a native sub-issue parent per QEP-2 on 2026-08-13: every open phase item above is now a sub-issue (#146–#158, annotated inline), the infrastructure Type label is removed in that order (the QEP's exemption from "unlabelled means needs triage" keys on the sub-issue relationship), and live status moves to the sub-issues — the checklist annotations are pointers, not a second status track. The #105 CLI-install gate and the #2/#18 overlaps stay where they are; those issues are related work, not children.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
This is an umbrella tracker whose live work is in sub-issues #146–#158, not one implementation. Start with PROJECT-OPTIMIZE-PREVIEWS.md and the selected sub-issue, then inspect the named entry points such as templates/ci.yml, consumer ci.yml files, and .github/workflows/build-containers.yml. Done means the chosen sub-issue's scoped change is verified and its status is tracked there.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- docker, github-actions, shell
- Domain
- build-system, ci-cd, devops, performance
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100