Skip to content

Warm the Start server graph on isolates that persist - #1679

Merged
RhysSullivan merged 1 commit into
mainfrom
perf/warm-start-graph-persistent-isolates
Aug 18, 2026
Merged

RhysSullivan merged 1 commit into
mainfrom
perf/warm-start-graph-persistent-isolates

Conversation

@RhysSullivan

Copy link
Copy Markdown
Collaborator

The measurement

Timing Start's loadEntries import apart from the request's own work, on a cold isolate in production:

p50
loadEntries import of the server graph 3104ms
the request's own work, once loaded 33ms
a warm isolate, whole handler 9-104ms

The same production bundle in local workerd serves the same path in ~3ms. So the graph is not slow code — it is a cold-isolate cost on production metal (4.08MB gzipped, 203 chunks).

And nearly every page request is cold: /mcp dispatches before fetchHandler, so MCP traffic (~14.4k req/hr) creates and occupies isolates without ever loading the graph. Page requests land on those — worker.dispatch ran 1,666 requests across 1,608 isolates, and wasWarm was false on every sampled request.

Ruled out along the way: OOM churn (0 exceededMemory in 24h), CPU (67-357ms against 3.1s), the Shiki grammars (lazily imported; start-*.js never references the highlighter chunk), and smoke-render (3.15MB but dynamic).

Why this is not just a re-land of #1628

#1628 warmed on every isolate's first fetch and was reverted for good reason: it pulled the SSR graph into every isolate including MCP-only ones, and memory-limit kills went from ~500-900 to 3.7k-9.4k per 5min.

The memory blowup was made of single-shot isolates that never lived to use the graph. So warming now waits for evidence the isolate persists — it warms only after the isolate has served a couple of fetches. An isolate that has handled several requests is one that sticks around, which is exactly the kind a later page request can land on. Ephemeral isolates are never warmed.

Baseline memory kills are currently zero, so there is headroom, and START_GRAPH_WARM=false turns it off without a deploy (START_GRAPH_WARM_AFTER tunes the threshold).

Verification

format, lint (0 errors), typecheck (44/44), apps/cloud vitest 280 passed.

Post-merge I will watch exceededMemory and page-request latency, and flip the switch if kills move.

Timing the import apart from the request's own work in production: the
loadEntries import is p50 3.1s, the request's work is p50 33ms, and a warm
isolate answers in 9-104ms. The same bundle in local workerd serves the
same path in ~3ms, so this is a cold-isolate cost on production metal, not
slow code.

Nearly every page request pays it: /mcp dispatches before fetchHandler, so
MCP traffic creates and occupies isolates without ever loading the graph,
and worker.dispatch ran 1,666 requests across 1,608 isolates.

#1628 warmed on every isolate's first fetch and was reverted because it
pulled the SSR graph into every isolate including single-shot MCP ones,
taking memory kills from ~500-900 to 3.7k-9.4k per 5min. Wait for evidence
the isolate persists instead: warm only after it has served a couple of
fetches. START_GRAPH_WARM=false disables without a deploy.
@RhysSullivan
RhysSullivan merged commit ca9617e into main Aug 18, 2026
@RhysSullivan
RhysSullivan deleted the perf/warm-start-graph-persistent-isolates branch August 18, 2026 23:49
@github-actions

github-actions Bot commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

Cloudflare preview

Torn down — the PR is closed.

RhysSullivan added a commit that referenced this pull request Aug 19, 2026
Isolates ARE reused - those serving a page request had already served a
median of 19 spans across 3 paths - they are just never warm for Start,
because /mcp returns before fetchHandler.

Two import-based warmups (#1679, #1681) moved nothing: loadEntries awaits
three specifiers plus a separately cached manifest, so warming a subset
leaves the request paying for the rest. Drive the real handler instead;
it populates whatever those caches are and cannot drift with Start's
internals.
RhysSullivan added a commit that referenced this pull request Aug 19, 2026
* Revert "Fix lint: do not stringify the unknown warmup error (#1688)"

This reverts commit 1c57887.

* Revert "Log whether the warmup runs and whether warm isolates still pay for Start (#1687)"

This reverts commit 1b2509c.

* Revert "Warm the Start graph by replaying a real request per isolate (#1686)"

This reverts commit 46625d2.

* Reapply "Warm the Start server graph on isolate first fetch (#1681)" (#1682)

This reverts commit aff1f39.

* Revert "Warm the Start server graph on isolate first fetch (#1681)"

This reverts commit 29a0aea.

* Reapply "Warm the Start server graph on isolates that persist (#1679)" (#1680)

This reverts commit 332955f.

* Revert "Warm the Start server graph on isolates that persist (#1679)"

This reverts commit ca9617e.
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