Skip to content

QuickJS engine: threshold-based VM-memory snapshotting (WORKFLOW_SNAPSHOT_THRESHOLD) - #3053

Draft
TooTallNate wants to merge 1 commit into
quickjs-vm-snapshotsfrom
quickjs-vm-threshold-snapshots
Draft

QuickJS engine: threshold-based VM-memory snapshotting (WORKFLOW_SNAPSHOT_THRESHOLD)#3053
TooTallNate wants to merge 1 commit into
quickjs-vm-snapshotsfrom
quickjs-vm-threshold-snapshots

Conversation

@TooTallNate

Copy link
Copy Markdown
Member

Stacked PR — based on #3050 (quickjs-vm-snapshots) ← #3049#3048. Review only the top commit here until the bases merge.

Summary

PR 4 of the QuickJS VM roadmap: threshold-based VM-memory snapshotting — the middle ground that motivated reviving this effort (see #1298 / #1300 discussion). Instead of snapshotting at every suspension (the original branch's model, which cost ~25% on e2e wall clock), snapshots are taken only once WORKFLOW_SNAPSHOT_THRESHOLD events have been processed since the last one:

  • Short-lived runs never snapshot — they keep PR 1/2's pure replay behavior with zero snapshot overhead.
  • Long/forever runs stop scaling their resume cost with event-log length — a resumption restores the VM heap and replays only the delta events since the snapshot's cursor.

How it works

  • WORKFLOW_SNAPSHOT_THRESHOLD env var (default 0 = disabled) or per-run executionContext.snapshotThreshold, stamped at start() for run affinity like WORKFLOW_VM.
  • Save (suspension exit, threshold met): capture live VM memory (session.snapshot()) → compress (zstd/gzip via the shared serialization pipeline; QuickJS heaps compress ~4×, measured 16.5 MB → 3.9 MB) → encrypt with the run's key when configured → world.snapshots.save with the events cursor at the VM's feed frontier.
  • Restore (subsequent invocation): world.snapshots.load → decrypt → decompress → QuickJS.restore over the cached WASM module, re-register host callbacks, fetch events from the snapshot's cursor and feed only the delta. Runs seamlessly through PR 2's inline continuation loop.
  • Delete on run completion/failure.
  • Fallback is always full replay: missing snapshot, load error, corrupt bytes, or restore failure logs a warning and boots fresh against the full event log — the log remains the source of truth; snapshots are strictly an optimization.

Determinism model (restore + partial replay)

The threshold model's new mechanism vs. the original branch: a resumption may restore a snapshot older than the log head (suspensions since the snapshot weren't persisted) and must deterministically re-derive everything in between:

  • The PRNG seed mixes in the restored snapshot's eventsCursor: the heap already consumed pre-snapshot draws, so re-seeding from the base would replay the first-N draws and collide with recorded correlationIds. The cursor is identical for every resume from the same snapshot (concurrent resumes still collide ids for the world's dedup) and advances only when a newer snapshot is taken.
  • Feeding already-consumed events is harmless by construction (consumed resolvers are gone; hook deliveries are deduped by eventId in the VM heap, which travels with the snapshot), so imprecise cursors only cost redundant scanning.
  • Covered by dedicated unit tests, including restore-from-older-snapshot with multi-suspension partial replay and identical post-restore correlationIds across concurrent resumes.

Validation

  • 135/135 e2e on nextjs-turbopack with WORKFLOW_SNAPSHOT_THRESHOLD=1 (maximum churn: snapshot on every qualifying suspension), wall clock within ~10% of the node baseline
  • Verified via debug diagnostics: restored: true resumptions, save/restore/delete lifecycle, and threshold gating (threshold=100 short run ⇒ zero snapshots, pure replay)
  • Full core unit suite green (1,573 tests); new tests for the config knobs and the snapshot/restore/partial-replay determinism
  • CI: new quickjs-snapshot matrix leg (nextjs-turbopack, threshold=1) across local dev/prod/postgres e2e jobs

Notes / follow-ups

  • Version skew: snapshot bytes are tied to the quickjs-wasi build that produced them. Per the deployment contract (runs continue on the version they started on — free on Vercel), this is a non-issue in production; environments without skew protection are covered by the restore-failure fallback to full replay.
  • An ID-divergence window exists when concurrent invocations resume from different snapshot generations; the world's per-(run, correlation) uniqueness rejects duplicates and the log-consistent invocation drives progress, with full-replay convergence as the backstop. Noted in code comments.
  • Docs: WORKFLOW_SNAPSHOT_THRESHOLD section added to v5 Runtime Tuning.

@changeset-bot

changeset-bot Bot commented Jul 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 64f40c3

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 16 packages
Name Type
@workflow/core Minor
workflow Minor
@workflow/builders Patch
@workflow/cli Patch
@workflow/next Patch
@workflow/nitro Patch
@workflow/vitest Patch
@workflow/web-shared Patch
@workflow/web Patch
@workflow/world-testing Patch
@workflow/astro Patch
@workflow/nest Patch
@workflow/nuxt Patch
@workflow/rollup Patch
@workflow/sveltekit Patch
@workflow/vite Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
example-nextjs-workflow-turbopack Ready Ready Preview, Comment Jul 22, 2026 3:37pm
example-nextjs-workflow-webpack Ready Ready Preview, Comment Jul 22, 2026 3:37pm
example-workflow Ready Ready Preview, Comment Jul 22, 2026 3:37pm
workbench-astro-workflow Ready Ready Preview, Comment Jul 22, 2026 3:37pm
workbench-express-workflow Ready Ready Preview, Comment Jul 22, 2026 3:37pm
workbench-fastify-workflow Ready Ready Preview, Comment Jul 22, 2026 3:37pm
workbench-hono-workflow Ready Ready Preview, Comment Jul 22, 2026 3:37pm
workbench-nestjs-workflow Ready Ready Preview, Comment Jul 22, 2026 3:37pm
workbench-nitro-workflow Ready Ready Preview, Comment Jul 22, 2026 3:37pm
workbench-nuxt-workflow Ready Ready Preview, Comment Jul 22, 2026 3:37pm
workbench-sveltekit-workflow Ready Ready Preview, Comment Jul 22, 2026 3:37pm
workbench-tanstack-start-workflow Ready Ready Preview, Comment Jul 22, 2026 3:37pm
workbench-vite-workflow Ready Ready Preview, Comment Jul 22, 2026 3:37pm
workflow-docs Ready Ready Preview, Comment, Open in v0 Jul 22, 2026 3:37pm
workflow-swc-playground Ready Ready Preview, Comment Jul 22, 2026 3:37pm
workflow-tarballs Ready Ready Preview, Comment Jul 22, 2026 3:37pm
workflow-web Ready Ready Preview, Comment Jul 22, 2026 3:37pm

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

Passed Failed Skipped Total
✅ ▲ Vercel Production 1455 0 239 1694
✅ 📦 Local Production 270 0 38 308
✅ 🐘 Local Postgres 270 0 38 308
✅ 🪟 Windows 154 0 0 154
✅ 📋 Other 252 0 56 308
✅ vercel-multi-region 27 0 0 27
Total 2428 0 371 2799

Details by Category

✅ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 126 0 28
✅ example 126 0 28
✅ express 126 0 28
✅ fastify 126 0 28
✅ hono 126 0 28
✅ nextjs-turbopack 151 0 3
✅ nextjs-webpack 151 0 3
✅ nitro 126 0 28
✅ nuxt 126 0 28
✅ sveltekit 145 0 9
✅ vite 126 0 28
✅ 📦 Local Production
App Passed Failed Skipped
✅ nextjs-turbopack-canary 135 0 19
✅ nextjs-webpack-canary 135 0 19
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ nextjs-turbopack-canary 135 0 19
✅ nextjs-webpack-canary 135 0 19
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 154 0 0
✅ 📋 Other
App Passed Failed Skipped
✅ e2e-vercel-prod-nest 126 0 28
✅ e2e-vercel-prod-tanstack-start 126 0 28
✅ vercel-multi-region
App Passed Failed Skipped
✅ nextjs-turbopack 27 0 0

📋 View full workflow run


Some E2E test jobs failed:

  • Vercel Prod: success
  • Local Dev: failure
  • Local Prod: failure
  • Local Postgres: failure
  • Windows: success

Check the workflow run for details.

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