From a92f13b3fd8324c09ff7fe747912cc86db4e861c Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:17:25 -0700 Subject: [PATCH 1/7] Revert "Fix lint: do not stringify the unknown warmup error (#1688)" This reverts commit 1c57887ba90d23d778eab2713f2d5f56fd7cfb03. --- apps/cloud/src/server.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/cloud/src/server.ts b/apps/cloud/src/server.ts index 7566ad45e9..9821de48f8 100644 --- a/apps/cloud/src/server.ts +++ b/apps/cloud/src/server.ts @@ -253,7 +253,7 @@ const warmStartGraph = (env: Env, ctx: ExecutionContext): void => { ctx, ); console.log(JSON.stringify({ probe: "warm", ok: true, ms: Date.now() - startedAt })); - } catch { + } catch (err) { // Advisory only — the request path still loads the graph lazily. startGraphWarmupStarted = false; console.log( @@ -261,6 +261,7 @@ const warmStartGraph = (env: Env, ctx: ExecutionContext): void => { probe: "warm", ok: false, ms: Date.now() - startedAt, + err: String(err), }), ); } From 892dbca4e396977bb2874c0680ca0ac50570c19c Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:17:25 -0700 Subject: [PATCH 2/7] Revert "Log whether the warmup runs and whether warm isolates still pay for Start (#1687)" This reverts commit 1b2509c9f688df6d4a53ecf23f03428fe4e3db48. --- apps/cloud/src/server.ts | 38 ++------------------------------------ 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/apps/cloud/src/server.ts b/apps/cloud/src/server.ts index 9821de48f8..828e408878 100644 --- a/apps/cloud/src/server.ts +++ b/apps/cloud/src/server.ts @@ -103,36 +103,12 @@ export { McpExecutionOwnerDirectoryDO } from "@executor-js/cloudflare/mcp/execut // until the in-flight export resolves. // --------------------------------------------------------------------------- -const rawFetchHandler = handler.fetch as ( +const fetchHandler = handler.fetch as ( request: Request, env: Env, ctx: ExecutionContext, ) => Response | Promise; -// TEMPORARY: does an isolate that already warmed still pay for Start? -let startEverHandled = false; -const fetchHandler = async ( - request: Request, - env: Env, - ctx: ExecutionContext, -): Promise => { - const wasWarm = startEverHandled; - startEverHandled = true; - await scheduler.wait(0); - const startedAt = Date.now(); - const response = await rawFetchHandler(request, env, ctx); - await scheduler.wait(0); - console.log( - JSON.stringify({ - probe: "start", - path: new URL(request.url).pathname, - wasWarm, - ms: Date.now() - startedAt, - }), - ); - return response; -}; - const tracer = trace.getTracer("executor-cloud-worker"); const traceparentValueFor = (spanContext: SpanContext): string => @@ -244,7 +220,6 @@ const warmStartGraph = (env: Env, ctx: ExecutionContext): void => { ctx.waitUntil( (async () => { - const startedAt = Date.now(); // oxlint-disable-next-line executor/no-try-catch-or-throw -- adapter boundary; a warmup failure must never affect the request that triggered it try { await fetchHandler( @@ -252,18 +227,9 @@ const warmStartGraph = (env: Env, ctx: ExecutionContext): void => { env, ctx, ); - console.log(JSON.stringify({ probe: "warm", ok: true, ms: Date.now() - startedAt })); - } catch (err) { + } catch { // Advisory only — the request path still loads the graph lazily. startGraphWarmupStarted = false; - console.log( - JSON.stringify({ - probe: "warm", - ok: false, - ms: Date.now() - startedAt, - err: String(err), - }), - ); } })(), ); From 6c58d57761e6c7a16ac1e0da18119b5a009222a0 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:17:25 -0700 Subject: [PATCH 3/7] Revert "Warm the Start graph by replaying a real request per isolate (#1686)" This reverts commit 46625d2090f58fe5cfd236872dc4a37a8ea1cc06. --- apps/cloud/src/env-augment.d.ts | 2 -- apps/cloud/src/server.ts | 62 --------------------------------- 2 files changed, 64 deletions(-) diff --git a/apps/cloud/src/env-augment.d.ts b/apps/cloud/src/env-augment.d.ts index 952d5c9978..cb8070ae63 100644 --- a/apps/cloud/src/env-augment.d.ts +++ b/apps/cloud/src/env-augment.d.ts @@ -95,8 +95,6 @@ declare global { MCP_REQUEST_STATE_KEY?: string; /** Emergency rollback for inbound MCP 2026-07-28 traffic only. */ MCP_2026_07_28_ENABLED?: string; - // Kill switch for the Start server-graph warmup (server.ts). - START_GRAPH_WARM?: string; NODE_ENV?: string; // Shared with frontend diff --git a/apps/cloud/src/server.ts b/apps/cloud/src/server.ts index 828e408878..0304ab0246 100644 --- a/apps/cloud/src/server.ts +++ b/apps/cloud/src/server.ts @@ -178,70 +178,8 @@ const mcpAgentHandler = makeCloudMcpAgentHandler({ traceRequest: traceCloudMcpRequest, }); -// --------------------------------------------------------------------------- -// Start server-graph warmup -// --------------------------------------------------------------------------- -// -// Page latency was p50 19-49ms daily through 2026-08-16 and 2728ms the day -// after the 21:20 deploy. Not load: 2026-08-11 served MORE traffic (5.43M vs -// 4.38M requests) with pages at p50 41ms. -// -// What changed is where MCP work runs. The old hibernatable Agent bridge handed -// `/mcp` to the Durable Object almost immediately; the v2 stack authenticates -// and dispatches in the worker. `/mcp` returns above without touching -// `fetchHandler`, so an isolate can serve a lot of MCP traffic and still have -// never loaded the Start graph. Measured: isolates that served a page request -// had already served a median of 19 spans across 3 paths — they are reused, -// they are just cold for Start. The page request then pays `loadEntries`: p50 -// **3.1s**, against p50 33ms for the request's own work and 9-104ms warm. The -// same bundle in local workerd serves the same path in ~3ms. -// -// Warm by REPLAYING A REAL REQUEST through the handler, not by importing the -// virtual entry ids. Two attempts at the import approach (#1679, #1681) moved -// nothing in production: `loadEntries` awaits THREE specifiers -// (`#tanstack-router-entry`, `#tanstack-start-entry`, -// `#tanstack-start-plugin-adapters`) plus a separately-cached manifest, so -// warming a subset leaves the request paying for the rest. Driving the real -// handler populates whatever those caches are, by construction, and cannot -// drift when Start changes its internals. -// -// `/robots.txt` is the cheapest Start-served route. It runs once per isolate, -// under `waitUntil` so it outlives the request that triggered it, and costs one -// extra synthetic request per isolate in telemetry. -// `START_GRAPH_WARM=false` disables it without a deploy. -// --------------------------------------------------------------------------- - -let startGraphWarmupStarted = false; - -const warmStartGraph = (env: Env, ctx: ExecutionContext): void => { - if (startGraphWarmupStarted) return; - if (env.START_GRAPH_WARM === "false") return; - startGraphWarmupStarted = true; - - ctx.waitUntil( - (async () => { - // oxlint-disable-next-line executor/no-try-catch-or-throw -- adapter boundary; a warmup failure must never affect the request that triggered it - try { - await fetchHandler( - new Request("https://executor.sh/robots.txt", { method: "GET" }), - env, - ctx, - ); - } catch { - // Advisory only — the request path still loads the graph lazily. - startGraphWarmupStarted = false; - } - })(), - ); -}; - const cloudflareHandler: ExportedHandler = { fetch: async (request, env, ctx) => { - // First fetch in this isolate drives one real request through Start in the - // background, including for /mcp — MCP traffic is what reaches these - // isolates first, and is why they are otherwise never warmed. - warmStartGraph(env, ctx); - // Public pages must not enter TanStack Start: its first-request dynamic // import loads the entire React + Effect server graph and can take seconds // on a cold isolate. Classify and service-bind marketing at the Worker From a7e5869da682780b91927e81bcb82af79bdff13c Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:17:25 -0700 Subject: [PATCH 4/7] Reapply "Warm the Start server graph on isolate first fetch (#1681)" (#1682) This reverts commit aff1f3946bb57acfd05336329589f6f6ca3d9e24. --- apps/cloud/src/env-augment.d.ts | 3 ++ apps/cloud/src/server.ts | 51 +++++++++++++++++++++++ apps/cloud/src/start-virtual-entries.d.ts | 8 ++++ 3 files changed, 62 insertions(+) create mode 100644 apps/cloud/src/start-virtual-entries.d.ts diff --git a/apps/cloud/src/env-augment.d.ts b/apps/cloud/src/env-augment.d.ts index cb8070ae63..88b300ccb5 100644 --- a/apps/cloud/src/env-augment.d.ts +++ b/apps/cloud/src/env-augment.d.ts @@ -95,6 +95,9 @@ declare global { MCP_REQUEST_STATE_KEY?: string; /** Emergency rollback for inbound MCP 2026-07-28 traffic only. */ MCP_2026_07_28_ENABLED?: string; + // Kill switch for the Start server-graph warmup (server.ts). Set to + // "false" to disable without a deploy if memory pressure returns. + START_GRAPH_WARM?: string; NODE_ENV?: string; // Shared with frontend diff --git a/apps/cloud/src/server.ts b/apps/cloud/src/server.ts index 0304ab0246..11e88cb5c8 100644 --- a/apps/cloud/src/server.ts +++ b/apps/cloud/src/server.ts @@ -178,8 +178,59 @@ const mcpAgentHandler = makeCloudMcpAgentHandler({ traceRequest: traceCloudMcpRequest, }); +// --------------------------------------------------------------------------- +// Start server-graph warmup +// --------------------------------------------------------------------------- +// +// Page latency was p50 19-49ms every day up to 2026-08-16 and 2728ms the day +// after the 21:20 UTC deploy. It is not load: 2026-08-11 served MORE total +// traffic (5.43M requests vs 4.38M) with pages at p50 41ms, so the +// reconnect-storm/volume explanation does not hold. +// +// What changed is where MCP work runs. The old hibernatable Agent bridge +// handed `/mcp` to the Durable Object almost immediately; the v2 stack +// authenticates and dispatches in the WORKER (`mcp.auth.jwt_verify` alone runs +// ~35k times per 6h there). That spins up far more worker isolates, and `/mcp` +// returns above without ever touching `fetchHandler` — so those isolates never +// load the Start graph. Page requests then land on them cold and pay +// `loadEntries`: measured p50 **3.1s**, against p50 33ms for the request's own +// work and 9-104ms for a warm isolate. The same bundle in local workerd serves +// the same path in ~3ms, so this is a cold-isolate cost, not slow code. +// +// So warm on the isolate's FIRST fetch, in the background: MCP traffic then +// pre-warms an isolate before a page request reaches it. This is #1628, which +// worked; it was reverted (#1634) when memory-limit kills jumped, but that +// landed during the 2026-08-17 storm — 11.67M requests in a day against a +// 2.6-4.3M baseline. Volume is back to baseline and `exceededMemory` is +// currently zero, so the condition that made it expensive is gone. +// `START_GRAPH_WARM=false` disables it without a deploy. +// +// Deliberately NOT at module scope: a full warmup there trips workerd's +// global-scope I/O restriction, and DO-only isolates should not carry the SSR +// graph. +// --------------------------------------------------------------------------- + +let startGraphWarmupStarted = false; + +const warmStartGraph = (env: Env): void => { + if (startGraphWarmupStarted) return; + if (env.START_GRAPH_WARM === "false") return; + startGraphWarmupStarted = true; + // oxlint-disable-next-line executor/no-promise-catch -- adapter boundary; fire-and-forget warmup outside any Effect runtime + void Promise.all([import("#tanstack-router-entry"), import("#tanstack-start-entry")]).catch( + () => { + // Advisory only — the request path still loads the graph lazily. + startGraphWarmupStarted = false; + }, + ); +}; + const cloudflareHandler: ExportedHandler = { fetch: async (request, env, ctx) => { + // First fetch in this isolate kicks the graph load off in the background, + // including for /mcp — MCP traffic is what reaches these isolates first. + warmStartGraph(env); + // Public pages must not enter TanStack Start: its first-request dynamic // import loads the entire React + Effect server graph and can take seconds // on a cold isolate. Classify and service-bind marketing at the Worker diff --git a/apps/cloud/src/start-virtual-entries.d.ts b/apps/cloud/src/start-virtual-entries.d.ts new file mode 100644 index 0000000000..9fb43d8206 --- /dev/null +++ b/apps/cloud/src/start-virtual-entries.d.ts @@ -0,0 +1,8 @@ +// TanStack Start's internal virtual server-entry modules (registered by the +// Start vite plugin; the same ids `start-server-core`'s `loadEntries` +// imports). server.ts imports them for the isolate warmup — only the +// module-evaluation side effect matters there, so the value shape is left +// untyped. Kept in a standalone declaration file: shorthand ambient modules +// only register from a non-module file (env-augment.d.ts is a module). +declare module "#tanstack-router-entry"; +declare module "#tanstack-start-entry"; From ab283b60baf295ac7b5a45e2af72e837a3e6e7ae Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:17:25 -0700 Subject: [PATCH 5/7] Revert "Warm the Start server graph on isolate first fetch (#1681)" This reverts commit 29a0aea7e11f8ae54577c5bb3f62fc94302d2a0d. --- apps/cloud/src/env-augment.d.ts | 3 -- apps/cloud/src/server.ts | 51 ----------------------- apps/cloud/src/start-virtual-entries.d.ts | 8 ---- 3 files changed, 62 deletions(-) delete mode 100644 apps/cloud/src/start-virtual-entries.d.ts diff --git a/apps/cloud/src/env-augment.d.ts b/apps/cloud/src/env-augment.d.ts index 88b300ccb5..cb8070ae63 100644 --- a/apps/cloud/src/env-augment.d.ts +++ b/apps/cloud/src/env-augment.d.ts @@ -95,9 +95,6 @@ declare global { MCP_REQUEST_STATE_KEY?: string; /** Emergency rollback for inbound MCP 2026-07-28 traffic only. */ MCP_2026_07_28_ENABLED?: string; - // Kill switch for the Start server-graph warmup (server.ts). Set to - // "false" to disable without a deploy if memory pressure returns. - START_GRAPH_WARM?: string; NODE_ENV?: string; // Shared with frontend diff --git a/apps/cloud/src/server.ts b/apps/cloud/src/server.ts index 11e88cb5c8..0304ab0246 100644 --- a/apps/cloud/src/server.ts +++ b/apps/cloud/src/server.ts @@ -178,59 +178,8 @@ const mcpAgentHandler = makeCloudMcpAgentHandler({ traceRequest: traceCloudMcpRequest, }); -// --------------------------------------------------------------------------- -// Start server-graph warmup -// --------------------------------------------------------------------------- -// -// Page latency was p50 19-49ms every day up to 2026-08-16 and 2728ms the day -// after the 21:20 UTC deploy. It is not load: 2026-08-11 served MORE total -// traffic (5.43M requests vs 4.38M) with pages at p50 41ms, so the -// reconnect-storm/volume explanation does not hold. -// -// What changed is where MCP work runs. The old hibernatable Agent bridge -// handed `/mcp` to the Durable Object almost immediately; the v2 stack -// authenticates and dispatches in the WORKER (`mcp.auth.jwt_verify` alone runs -// ~35k times per 6h there). That spins up far more worker isolates, and `/mcp` -// returns above without ever touching `fetchHandler` — so those isolates never -// load the Start graph. Page requests then land on them cold and pay -// `loadEntries`: measured p50 **3.1s**, against p50 33ms for the request's own -// work and 9-104ms for a warm isolate. The same bundle in local workerd serves -// the same path in ~3ms, so this is a cold-isolate cost, not slow code. -// -// So warm on the isolate's FIRST fetch, in the background: MCP traffic then -// pre-warms an isolate before a page request reaches it. This is #1628, which -// worked; it was reverted (#1634) when memory-limit kills jumped, but that -// landed during the 2026-08-17 storm — 11.67M requests in a day against a -// 2.6-4.3M baseline. Volume is back to baseline and `exceededMemory` is -// currently zero, so the condition that made it expensive is gone. -// `START_GRAPH_WARM=false` disables it without a deploy. -// -// Deliberately NOT at module scope: a full warmup there trips workerd's -// global-scope I/O restriction, and DO-only isolates should not carry the SSR -// graph. -// --------------------------------------------------------------------------- - -let startGraphWarmupStarted = false; - -const warmStartGraph = (env: Env): void => { - if (startGraphWarmupStarted) return; - if (env.START_GRAPH_WARM === "false") return; - startGraphWarmupStarted = true; - // oxlint-disable-next-line executor/no-promise-catch -- adapter boundary; fire-and-forget warmup outside any Effect runtime - void Promise.all([import("#tanstack-router-entry"), import("#tanstack-start-entry")]).catch( - () => { - // Advisory only — the request path still loads the graph lazily. - startGraphWarmupStarted = false; - }, - ); -}; - const cloudflareHandler: ExportedHandler = { fetch: async (request, env, ctx) => { - // First fetch in this isolate kicks the graph load off in the background, - // including for /mcp — MCP traffic is what reaches these isolates first. - warmStartGraph(env); - // Public pages must not enter TanStack Start: its first-request dynamic // import loads the entire React + Effect server graph and can take seconds // on a cold isolate. Classify and service-bind marketing at the Worker diff --git a/apps/cloud/src/start-virtual-entries.d.ts b/apps/cloud/src/start-virtual-entries.d.ts deleted file mode 100644 index 9fb43d8206..0000000000 --- a/apps/cloud/src/start-virtual-entries.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -// TanStack Start's internal virtual server-entry modules (registered by the -// Start vite plugin; the same ids `start-server-core`'s `loadEntries` -// imports). server.ts imports them for the isolate warmup — only the -// module-evaluation side effect matters there, so the value shape is left -// untyped. Kept in a standalone declaration file: shorthand ambient modules -// only register from a non-module file (env-augment.d.ts is a module). -declare module "#tanstack-router-entry"; -declare module "#tanstack-start-entry"; From 6b37e009f01ec367f4deaf635d2ac02181700338 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:17:25 -0700 Subject: [PATCH 6/7] Reapply "Warm the Start server graph on isolates that persist (#1679)" (#1680) This reverts commit 332955f80a83d432779135ab684e7259b6a0c864. --- apps/cloud/src/env-augment.d.ts | 6 +++ apps/cloud/src/server.ts | 60 +++++++++++++++++++++++ apps/cloud/src/start-virtual-entries.d.ts | 8 +++ 3 files changed, 74 insertions(+) create mode 100644 apps/cloud/src/start-virtual-entries.d.ts diff --git a/apps/cloud/src/env-augment.d.ts b/apps/cloud/src/env-augment.d.ts index cb8070ae63..c163d61b9d 100644 --- a/apps/cloud/src/env-augment.d.ts +++ b/apps/cloud/src/env-augment.d.ts @@ -95,6 +95,12 @@ declare global { MCP_REQUEST_STATE_KEY?: string; /** Emergency rollback for inbound MCP 2026-07-28 traffic only. */ MCP_2026_07_28_ENABLED?: string; + // Kill switch for the Start server-graph warmup (server.ts). Set to + // "false" to disable without a deploy if it ever costs memory again. + START_GRAPH_WARM?: string; + // How many fetches an isolate must serve before it is considered + // persistent enough to warm. Defaults to 2. + START_GRAPH_WARM_AFTER?: string; NODE_ENV?: string; // Shared with frontend diff --git a/apps/cloud/src/server.ts b/apps/cloud/src/server.ts index 0304ab0246..52da6cd201 100644 --- a/apps/cloud/src/server.ts +++ b/apps/cloud/src/server.ts @@ -178,8 +178,68 @@ const mcpAgentHandler = makeCloudMcpAgentHandler({ traceRequest: traceCloudMcpRequest, }); +// --------------------------------------------------------------------------- +// Start server-graph warmup +// --------------------------------------------------------------------------- +// +// TanStack Start loads the router + start instance behind a dynamic import on +// the first Start-handled request per isolate (`start-server-core`'s +// `loadEntries`). Measured in production by timing that import apart from the +// request's own work: the import is **p50 3.1s**, the 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` is dispatched above, before +// `fetchHandler`, so MCP traffic — the large majority — creates and occupies +// isolates without ever loading the graph. Page requests then land on those: +// `worker.dispatch` ran 1,666 requests across 1,608 isolates (1.04 each). +// +// #1628 warmed on every isolate's FIRST fetch and was reverted: 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 fix for that is to stop +// warming isolates that will not live long enough to use it. An isolate that +// has already served several requests is one that persists — exactly the kind +// a later page request can land on — so warming waits for that evidence. +// Single-shot isolates, which is what the memory blowup was made of, are never +// warmed. +// +// Deliberately NOT at module scope: a full warmup there trips workerd's +// global-scope I/O restriction, and DO-only isolates should not carry the SSR +// graph. `START_GRAPH_WARM=false` disables it without a deploy. +// --------------------------------------------------------------------------- + +const DEFAULT_WARM_AFTER_FETCHES = 2; + +let isolateFetchCount = 0; +let startGraphWarmupStarted = false; + +const maybeWarmStartGraph = (env: Env): void => { + if (startGraphWarmupStarted) return; + if (env.START_GRAPH_WARM === "false") return; + + isolateFetchCount += 1; + const parsed = Number.parseInt(env.START_GRAPH_WARM_AFTER ?? "", 10); + const warmAfter = Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_WARM_AFTER_FETCHES; + if (isolateFetchCount < warmAfter) return; + + startGraphWarmupStarted = true; + // oxlint-disable-next-line executor/no-promise-catch -- adapter boundary; fire-and-forget warmup outside any Effect runtime + void Promise.all([import("#tanstack-router-entry"), import("#tanstack-start-entry")]).catch( + () => { + // Advisory only — the request path still loads the graph lazily. + startGraphWarmupStarted = false; + }, + ); +}; + const cloudflareHandler: ExportedHandler = { fetch: async (request, env, ctx) => { + // Every request counts toward this isolate's persistence, including /mcp + // (which returns below without ever loading the graph) — MCP traffic is + // precisely what keeps these isolates alive for a later page request. + maybeWarmStartGraph(env); + // Public pages must not enter TanStack Start: its first-request dynamic // import loads the entire React + Effect server graph and can take seconds // on a cold isolate. Classify and service-bind marketing at the Worker diff --git a/apps/cloud/src/start-virtual-entries.d.ts b/apps/cloud/src/start-virtual-entries.d.ts new file mode 100644 index 0000000000..9fb43d8206 --- /dev/null +++ b/apps/cloud/src/start-virtual-entries.d.ts @@ -0,0 +1,8 @@ +// TanStack Start's internal virtual server-entry modules (registered by the +// Start vite plugin; the same ids `start-server-core`'s `loadEntries` +// imports). server.ts imports them for the isolate warmup — only the +// module-evaluation side effect matters there, so the value shape is left +// untyped. Kept in a standalone declaration file: shorthand ambient modules +// only register from a non-module file (env-augment.d.ts is a module). +declare module "#tanstack-router-entry"; +declare module "#tanstack-start-entry"; From c8c9e91653d104896c4a668cbe25e46e6cd0da97 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Wed, 19 Aug 2026 07:17:25 -0700 Subject: [PATCH 7/7] Revert "Warm the Start server graph on isolates that persist (#1679)" This reverts commit ca9617e89b7d9e38a87918864e1911bee4019dd9. --- apps/cloud/src/env-augment.d.ts | 6 --- apps/cloud/src/server.ts | 60 ----------------------- apps/cloud/src/start-virtual-entries.d.ts | 8 --- 3 files changed, 74 deletions(-) delete mode 100644 apps/cloud/src/start-virtual-entries.d.ts diff --git a/apps/cloud/src/env-augment.d.ts b/apps/cloud/src/env-augment.d.ts index c163d61b9d..cb8070ae63 100644 --- a/apps/cloud/src/env-augment.d.ts +++ b/apps/cloud/src/env-augment.d.ts @@ -95,12 +95,6 @@ declare global { MCP_REQUEST_STATE_KEY?: string; /** Emergency rollback for inbound MCP 2026-07-28 traffic only. */ MCP_2026_07_28_ENABLED?: string; - // Kill switch for the Start server-graph warmup (server.ts). Set to - // "false" to disable without a deploy if it ever costs memory again. - START_GRAPH_WARM?: string; - // How many fetches an isolate must serve before it is considered - // persistent enough to warm. Defaults to 2. - START_GRAPH_WARM_AFTER?: string; NODE_ENV?: string; // Shared with frontend diff --git a/apps/cloud/src/server.ts b/apps/cloud/src/server.ts index 52da6cd201..0304ab0246 100644 --- a/apps/cloud/src/server.ts +++ b/apps/cloud/src/server.ts @@ -178,68 +178,8 @@ const mcpAgentHandler = makeCloudMcpAgentHandler({ traceRequest: traceCloudMcpRequest, }); -// --------------------------------------------------------------------------- -// Start server-graph warmup -// --------------------------------------------------------------------------- -// -// TanStack Start loads the router + start instance behind a dynamic import on -// the first Start-handled request per isolate (`start-server-core`'s -// `loadEntries`). Measured in production by timing that import apart from the -// request's own work: the import is **p50 3.1s**, the 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` is dispatched above, before -// `fetchHandler`, so MCP traffic — the large majority — creates and occupies -// isolates without ever loading the graph. Page requests then land on those: -// `worker.dispatch` ran 1,666 requests across 1,608 isolates (1.04 each). -// -// #1628 warmed on every isolate's FIRST fetch and was reverted: 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 fix for that is to stop -// warming isolates that will not live long enough to use it. An isolate that -// has already served several requests is one that persists — exactly the kind -// a later page request can land on — so warming waits for that evidence. -// Single-shot isolates, which is what the memory blowup was made of, are never -// warmed. -// -// Deliberately NOT at module scope: a full warmup there trips workerd's -// global-scope I/O restriction, and DO-only isolates should not carry the SSR -// graph. `START_GRAPH_WARM=false` disables it without a deploy. -// --------------------------------------------------------------------------- - -const DEFAULT_WARM_AFTER_FETCHES = 2; - -let isolateFetchCount = 0; -let startGraphWarmupStarted = false; - -const maybeWarmStartGraph = (env: Env): void => { - if (startGraphWarmupStarted) return; - if (env.START_GRAPH_WARM === "false") return; - - isolateFetchCount += 1; - const parsed = Number.parseInt(env.START_GRAPH_WARM_AFTER ?? "", 10); - const warmAfter = Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_WARM_AFTER_FETCHES; - if (isolateFetchCount < warmAfter) return; - - startGraphWarmupStarted = true; - // oxlint-disable-next-line executor/no-promise-catch -- adapter boundary; fire-and-forget warmup outside any Effect runtime - void Promise.all([import("#tanstack-router-entry"), import("#tanstack-start-entry")]).catch( - () => { - // Advisory only — the request path still loads the graph lazily. - startGraphWarmupStarted = false; - }, - ); -}; - const cloudflareHandler: ExportedHandler = { fetch: async (request, env, ctx) => { - // Every request counts toward this isolate's persistence, including /mcp - // (which returns below without ever loading the graph) — MCP traffic is - // precisely what keeps these isolates alive for a later page request. - maybeWarmStartGraph(env); - // Public pages must not enter TanStack Start: its first-request dynamic // import loads the entire React + Effect server graph and can take seconds // on a cold isolate. Classify and service-bind marketing at the Worker diff --git a/apps/cloud/src/start-virtual-entries.d.ts b/apps/cloud/src/start-virtual-entries.d.ts deleted file mode 100644 index 9fb43d8206..0000000000 --- a/apps/cloud/src/start-virtual-entries.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -// TanStack Start's internal virtual server-entry modules (registered by the -// Start vite plugin; the same ids `start-server-core`'s `loadEntries` -// imports). server.ts imports them for the isolate warmup — only the -// module-evaluation side effect matters there, so the value shape is left -// untyped. Kept in a standalone declaration file: shorthand ambient modules -// only register from a non-module file (env-augment.d.ts is a module). -declare module "#tanstack-router-entry"; -declare module "#tanstack-start-entry";