Skip to content

perf(host-cloudflare): cheaper new isolates: one-read D1 open and lazy QuickJS - #29

Merged
fforres merged 5 commits into
skywardfrom
perf/cold-isolate
Oct 18, 2026
Merged

fforres merged 5 commits into
skywardfrom
perf/cold-isolate

Conversation

@fforres

@fforres fforres commented Oct 18, 2026 •

Copy link
Copy Markdown

Why

A request that lands on a new isolate took about 1.5 to 1.9 s, against 0.1 to 0.3 s on a warm isolate. Two parts of the app build ran on every new isolate even when the request needed neither:

  • QuickJS: instantiated even for requests that never execute code.
  • D1 open: 7 queries on every open of a current database.

What changes

  • Lazy QuickJS through a runtime seam: runtime-quickjs gains setQuickJSModuleLoader.
    • resolveQuickJS uses the preloaded module first, then the registered loader (awaited once, memoized, reset on rejection so a transient failure retries), then getQuickJS().
    • Both makeQuickJsExecutor and executeSealedBundle resolve through it.
    • host-cloudflare registers its WASM-inlined loader once at module scope in worker.ts, so the fetch handlers and the MCP Durable Objects are all covered, and drops every per-build preloadQuickJs call.
    • The artifact smoke-render (executeSealedBundle in the MCP session DO) loads QuickJS the same way. The first version of this PR missed that path.
  • One-read D1 open: the schema fingerprint also covers the source of ensureCloudflareD1SchemaCompatibility and the ordered data-migration ids.
    • A current database opens with one read and skips prepareCloudflareD1Data.
    • The expected fingerprint is computed once per isolate, and read in parallel with the stored marker.
    • The marker is still written only after DDL, compatibility and migrations succeed.

Tests

  • runtime-quickjs module-loader.test.ts: the loader runs only on the first resolve, and once across many resolves; a preloaded module wins; a rejected load retries; the sealed-bundle and executor paths share one load.
  • host-cloudflare:
    • Starting the worker and building the provider never invokes the loader.
    • D1: a current database opens with exactly one query. An outdated marker reruns migrations and rewrites it. A failed preparation keeps the old marker so the next open retries.
    • Every migration id reaches the ledger.
  • e2e cloudflare/artifact-smoke-render.test.ts: create-artifact over MCP with a component that throws on first render is refused with the thrown message, which proves QuickJS ran in workerd. It fails when the loader registration is removed.

https://claude.ai/code/session_019ZE3j9YzRKxT4LaiDCBv6S

Fold the compatibility version and the ordered data-migration ids into the
schema fingerprint and skip the compatibility check and migration ledger read
when the stored fingerprint matches. The marker is still written only after
DDL, compatibility and data migrations succeed.

Claude-Session: https://claude.ai/code/session_019ZE3j9YzRKxT4LaiDCBv6S
…f every app build

The QuickJS executor now loads the Workers WASM-inlined variant right before
its first execute, so building the app, the cron reconcile and MCP sessions
that never run code skip the load.

Claude-Session: https://claude.ai/code/session_019ZE3j9YzRKxT4LaiDCBv6S
…t loader

Hosts that cannot fetch the default WASM register a loader that runs on the
first resolve and is memoized, with a rejected load retried. The Cloudflare
worker registers its WASM-inlined variant once at module scope, so the
artifact smoke render in the MCP session Durable Object works again without
instantiating QuickJS at startup.

Claude-Session: https://claude.ai/code/session_019ZE3j9YzRKxT4LaiDCBv6S
…d simplify tests

The fingerprint now hashes the compatibility repair source instead of a
hand-bumped version, is computed once per isolate, and is read in parallel
with the stored marker. The D1 tests assert through the marker row and
recorded statements instead of a module mock.

Claude-Session: https://claude.ai/code/session_019ZE3j9YzRKxT4LaiDCBv6S
@fforres
fforres merged commit a59544d into skyward Oct 18, 2026
4 of 40 checks passed
@fforres
fforres deleted the perf/cold-isolate branch October 18, 2026 08:29
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