Skip to content

Commit a2f553e

Browse files
killaguclaude
andauthored
perf(ci): persist V8 compile cache across test jobs (#6009)
## What Enable Node's on-disk **V8 compile cache** (`NODE_COMPILE_CACHE`) for the three test jobs (`test`, `test-egg-bin`, `test-egg-scripts`) and persist it with `actions/cache`: - Job-level `env.NODE_COMPILE_CACHE: ${{ runner.temp }}/node-compile-cache` so the value is inherited by vitest workers and any forked child processes, not just the top-level process. - A `Cache Node compile cache` step (pinned `actions/cache@0057852…` # v4, the same pin already used for the utoo store) keyed `node-compile-cache-${{ runner.os }}-node${{ matrix.node }}-${{ hashFiles('pnpm-workspace.yaml', '**/package.json') }}` with graduated `restore-keys`, so the cache survives **within** a run (shared across processes) and **across** runs. `typecheck` is intentionally excluded — it runs `tsgo`/`oxlint`/`tsdown` (Rust), not Node module execution, so there is nothing for the V8 compile cache to help. Safety: Node validates each cache entry by source hash + Node version, so a stale restored entry is simply ignored (cache miss → recompile + rewrite). The change is low-risk and fully reversible. ## Honest measurement (please validate on CI before marking ready) Opened as **draft** because the local benefit I could measure is ~0 and the CI/Windows benefit is unverified: - On **macOS/arm64**, an A/B/C micro-benchmark of the egg-bin fork bootstrap (a full `egg-bin test` run, which populates **1346 cache files / 7.7 MB**) showed **no measurable speedup** — warm-cache runs landed inside the no-cache noise band (20.3–22.4s), and were marginally *slower* due to `flushCompileCache` write overhead on exit. - Reason: per-fork wall time is dominated by the **oxc transform** and **module import** (`transform 1.51s` + `import 1.56s` of a 2.9s run), plus process spawn — and the compile cache only caches the **V8 compilation of already-transpiled JS**, not the transform itself. - The case for keeping it rests on the **CI runners being much slower** at exactly the V8-compile + file-read work the cache removes (x64 ubuntu, and especially Windows) than an M-series Mac. That is plausible but **unmeasured here**. On Windows there is even a downside risk: a cache hit adds an extra file read per module (source + cache blob), and every file open is scanned by Defender, so it could be neutral or negative. **How to validate:** compare the test-job step durations run-over-run (the existing `Report parallelism metrics` job already surfaces CI timing), and/or check cache hit-rate in the Actions cache UI. Keep if it helps a platform; drop the env+step if not. ## Scope note This does **not** touch `tools/egg-bin/test/coffee.ts`, which forks the egg-bin CLI ~109× with a freshly-built `env` that does not pass `NODE_COMPILE_CACHE` through — so the compile cache does not currently reach those forks. That (and the Windows `test-egg-bin` slowdown generally) is being investigated separately on a Windows machine and is out of scope for this PR. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Improved CI build and test performance by enabling a Node.js compile-cache mechanism for relevant test jobs. * **Tests** * Ensured compile-cache tests run with a clean environment by resetting related cache settings before each test. * Made the subscription interval/cron test more reliable in CI by increasing the wait time before log-based assertions. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 9080894 commit a2f553e

3 files changed

Lines changed: 57 additions & 1 deletion

File tree

.github/workflows/ci.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,21 @@ jobs:
181181
with:
182182
node-version: ${{ matrix.node }}
183183

184+
- name: Cache Node compile cache
185+
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
186+
with:
187+
path: ${{ runner.temp }}/node-compile-cache
188+
key: node-compile-cache-${{ runner.os }}-node${{ matrix.node }}-${{ hashFiles('pnpm-workspace.yaml', '**/package.json') }}
189+
restore-keys: |
190+
node-compile-cache-${{ runner.os }}-node${{ matrix.node }}-
191+
node-compile-cache-${{ runner.os }}-
192+
193+
- name: Enable Node compile cache
194+
# runner.* is not available in job-level env, so export NODE_COMPILE_CACHE
195+
# here (bash on every OS) — every later step and forked child inherits it.
196+
shell: bash
197+
run: echo "NODE_COMPILE_CACHE=${{ runner.temp }}/node-compile-cache" >> "$GITHUB_ENV"
198+
184199
- name: Cache utoo package store
185200
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
186201
with:
@@ -260,6 +275,21 @@ jobs:
260275
with:
261276
node-version: ${{ matrix.node }}
262277

278+
- name: Cache Node compile cache
279+
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
280+
with:
281+
path: ${{ runner.temp }}/node-compile-cache
282+
key: node-compile-cache-${{ runner.os }}-node${{ matrix.node }}-${{ hashFiles('pnpm-workspace.yaml', '**/package.json') }}
283+
restore-keys: |
284+
node-compile-cache-${{ runner.os }}-node${{ matrix.node }}-
285+
node-compile-cache-${{ runner.os }}-
286+
287+
- name: Enable Node compile cache
288+
# runner.* is not available in job-level env, so export NODE_COMPILE_CACHE
289+
# here (bash on every OS) — every later step and forked child inherits it.
290+
shell: bash
291+
run: echo "NODE_COMPILE_CACHE=${{ runner.temp }}/node-compile-cache" >> "$GITHUB_ENV"
292+
263293
- name: Cache utoo package store
264294
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
265295
with:
@@ -326,6 +356,21 @@ jobs:
326356
with:
327357
node-version: ${{ matrix.node }}
328358

359+
- name: Cache Node compile cache
360+
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
361+
with:
362+
path: ${{ runner.temp }}/node-compile-cache
363+
key: node-compile-cache-${{ runner.os }}-node${{ matrix.node }}-${{ hashFiles('pnpm-workspace.yaml', '**/package.json') }}
364+
restore-keys: |
365+
node-compile-cache-${{ runner.os }}-node${{ matrix.node }}-
366+
node-compile-cache-${{ runner.os }}-
367+
368+
- name: Enable Node compile cache
369+
# runner.* is not available in job-level env, so export NODE_COMPILE_CACHE
370+
# here (bash on every OS) — every later step and forked child inherits it.
371+
shell: bash
372+
run: echo "NODE_COMPILE_CACHE=${{ runner.temp }}/node-compile-cache" >> "$GITHUB_ENV"
373+
329374
- name: Cache utoo package store
330375
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
331376
with:

packages/core/test/loader/manifest.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,15 @@ describe('ManifestStore', () => {
751751
const savedPortable = process.env.NODE_COMPILE_CACHE_PORTABLE;
752752
const savedDisable = process.env.NODE_DISABLE_COMPILE_CACHE;
753753

754+
beforeEach(() => {
755+
// enableCompileCache() respects an externally-set NODE_COMPILE_CACHE (CI
756+
// may set one job-wide). Clear the ambient vars so these tests exercise
757+
// the auto-set path from a clean slate; afterEach restores them.
758+
delete process.env.NODE_COMPILE_CACHE;
759+
delete process.env.NODE_COMPILE_CACHE_PORTABLE;
760+
delete process.env.NODE_DISABLE_COMPILE_CACHE;
761+
});
762+
754763
afterEach(() => {
755764
for (const [key, saved] of [
756765
['NODE_COMPILE_CACHE', savedCompileCache],

plugins/schedule/test/subscription.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ describe('cluster - subscription', () => {
1515
afterAll(() => app.close());
1616

1717
it('should support interval and cron', async () => {
18-
await sleep(5000);
18+
// interval is 4000ms; give the forked agent/worker boot + IPC + log flush
19+
// enough slack on busy CI runners so the task fires at least once.
20+
await sleep(process.env.CI ? 10000 : 5000);
1921

2022
const log = getLogContent('subscription');
2123
// console.log(log);

0 commit comments

Comments
 (0)