Skip to content

ci: keep the freeze cache alive and slim cold-build worker clones - #431

Merged
ShixiangWang merged 4 commits into
mainfrom
codex/ci-build-optimization
Nov 3, 2026
Merged

ShixiangWang merged 4 commits into
mainfrom
codex/ci-build-optimization

Conversation

@ShixiangWang

Copy link
Copy Markdown
Member

Problem

Run 41446738921 (PR #428) took ~3 h because it fell back to a full cold build. Phase timings from the log:

phase time
setup (renv restore + Julia env) ~31 min
Julia pre-render (40 notebooks, sequential) ~23 min
pre-render of 668 non-frozen QMDs (4 workers) ~85+ min
babelquarto + post-process ~20 min

The trigger: Restore freeze cache found nothing (⚠️ No freeze cache – full execution required), so every QMD re-executed.

Root cause: the freeze cache keeps getting evicted

GitHub's repo-wide Actions cache limit is 10 GB; the repo is at ~10.8 GB. Caches saved on a PR merge ref are visible only to that same PR, so the biggest savers add dead weight:

  • pr-review.yml → setup-r-dependencies with default cache: true saves ~2.3 GB per run that no other workflow can reuse.
  • setup-render-env → setup-renv saves ~1 GB per PR run, again visible only to that PR.

These evict the ~200 MB _freeze cache that every other build depends on. This recurs on every R version or renv.lock change (R 4.6.2 was released 2026-10-29 — yesterday — and the pool blew up today).

Upstream research (why no Quarto fork)

  • Quarto has no built-in parallel render; parallelization is an explicit goal of the open Execution Planner epic quarto-dev/quarto-cli#6518, which also tracks the file-conflict/race issues (#6116, #4730) that force per-worker clones. No parallel flag exists in current docs/releases.
  • Discussion #2749 confirms the recommended strategy is exactly what this repo already does: freeze + render-once-per-file into temp copies. Forking Quarto would duplicate the epic; the remaining wins here are in cache discipline and clone cost, not in Quarto itself.

Changes

  1. pr-review.yml — setup-r-dependencies gets cache: false (stops 2.3 GB dead-weight entries).
  2. setup-render-env — on pull_request, the renv cache is restored via actions/cache/restore with the exact key format setup-renv uses (verified against upstream action.yaml) and setup-renv runs with bypass-cache: true so it never saves on PRs. Push/schedule runs keep the default save path, so main still replenishes the shared pool.
  3. render-site + ci-benchmark — the per-file worker clone now excludes ./mcp (~109 MB of node assets no render touches) and local cache dirs, cutting clone I/O ~70% (≈150 GB less tar I/O across a 668-file cold run).

Tradeoffs

  • Re-runs of the same PR after a renv.lock change pay a full ~26 min install until the lockfile lands on main; a 2.3 GB dead-weight entry is worth more than the 200 MB freeze cache it evicts.
  • The PR restore-only renv path is not exercised by ci-benchmark.yml (manual, non-PR), which stays representative of the push/schedule pipeline.

Review

Independently reviewed (YAML validity, Actions semantics, setup-renv key parity, bash failure propagation, regression risks). A Julia pre-render parallelization was prototyped and rejected in review: concurrent quarto render in the shared workspace re-introduces the documented site_libs race that silently drops pages.

Closes parts of #306.

Root causes found in run 41446738921 (PR #428, ~3 h wall time):
- The repo-wide 10 GB cache pool was overflowing (~10.8 GB), so the
  _freeze cache got evicted and 668 QMD files re-executed (~85 min).
  Largest polluters: 2.3 GB setup-r-dependencies caches saved by
  pr-review.yml and ~1 GB renv caches saved per pull_request run
  (merge-ref caches are visible only to that same PR).
- Each pre-render worker tar-copied the full tree (~283 MB) per file;
  mcp/ alone is ~109 MB of node assets no render touches.

Fixes:
- pr-review.yml: disable the 2.3 GB setup-r-dependencies cache.
- setup-render-env: pull_request runs restore the renv cache but never
  save it (bypass-cache: true), with the same key format setup-renv
  uses so PRs still hit entries saved by main-branch runs.
- render-site + ci-benchmark: exclude mcp/ and local cache dirs from
  the per-file worker clone (~70% less clone I/O on cold runs).
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

✅ QMD Format Check

No new QMD tutorial files to check.

…ze entries

The per-file worker clones discarded the project-level
_freeze/site_libs/ directory, so a fresh render's html dependency
copies (e.g. simplelightbox-2.1.0 newly emitted by the repinned
pixture fork) never reached the shared freeze cache. Frozen renders
then reference assets that exist nowhere, producing dangling
site_libs links on the deployed site and aborting the zh dedup step
(prune-zh-duplicates LEFTOVER, first seen on PRs #426/#428).

- workers now rsync _freeze/site_libs/ back like the per-page dirs
- a self-heal step drops freeze pages whose execute-results/html.json
  references site_libs assets missing from the shared cache, so they
  re-render and repopulate it
@ShixiangWang

Copy link
Copy Markdown
Member Author

Added a follow-up fix for a related bug this PR's CI run exposed (also failing PRs #426/#428 independently): worker clones discarded the project-level _freeze/site_libs/, so the simplelightbox-2.1.0 dependency newly emitted by the repinned pixture fork never entered the shared freeze cache — every frozen ToolLinks render (en and zh) shipped a dangling stylesheet link and the zh dedup step aborted. Workers now sync _freeze/site_libs/ back, and a self-heal step drops freeze pages whose recorded references are missing from the shared cache so they re-render once.

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

✅ QMD Format Check

No new QMD tutorial files to check.

Review follow-ups: heal now runs before the Julia pre-render in the
benchmark (a healed Julia page could otherwise never be re-frozen),
Julia pages are skipped by the heal itself (the pre-render work list
never repopulates them), the rmtree target is guarded against a stray
_freeze/execute-results layout, refs must be quote/paren-anchored and
cannot contain .. segments, the worker site_libs rsync fails loudly,
and a large mass-drop emits a workflow warning.
@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

✅ QMD Format Check

No new QMD tutorial files to check.

- handle ./ and ../ page-relative site_libs prefixes (nested pages
  emit ../site_libs/...) and exclude JSON escape backslashes from the
  captured path
- a stale Julia freeze page is now reported instead of silently
  skipped by the loop guard
@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

✅ QMD Format Check

No new QMD tutorial files to check.

@ShixiangWang
ShixiangWang merged commit d5ec884 into main Nov 3, 2026
4 checks passed
@ShixiangWang
ShixiangWang deleted the codex/ci-build-optimization branch November 3, 2026 02:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant