| @@ -178,8 +178,59 @@ const mcpAgentHandler = makeCloudMcpAgentHandler({ · 177 unmodified lines |
| 178 | traceRequest: traceCloudMcpRequest, | 178 | traceRequest: traceCloudMcpRequest, |
| 179 | }); | 179 | }); |
| 180 | | 180 | |
| | 181 | // --------------------------------------------------------------------------- |
| | 182 | // Start server-graph warmup |
| | 183 | // --------------------------------------------------------------------------- |
| | 184 | // |
| | 185 | // Page latency was p50 19-49ms every day up to 2026-08-16 and 2728ms the day |
| | 186 | // after the 21:20 UTC deploy. It is not load: 2026-08-11 served MORE total |
| | 187 | // traffic (5.43M requests vs 4.38M) with pages at p50 41ms, so the |
| | 188 | // reconnect-storm/volume explanation does not hold. |
| | 189 | // |
| | 190 | // What changed is where MCP work runs. The old hibernatable Agent bridge |
| | 191 | // handed `/mcp` to the Durable Object almost immediately; the v2 stack |
| | 192 | // authenticates and dispatches in the WORKER (`mcp.auth.jwt_verify` alone runs |
| | 193 | // ~35k times per 6h there). That spins up far more worker isolates, and `/mcp` |
| | 194 | // returns above without ever touching `fetchHandler` — so those isolates never |
| | 195 | // load the Start graph. Page requests then land on them cold and pay |
| | 196 | // `loadEntries`: measured p50 **3.1s**, against p50 33ms for the request's own |
| | 197 | // work and 9-104ms for a warm isolate. The same bundle in local workerd serves |
| | 198 | // the same path in ~3ms, so this is a cold-isolate cost, not slow code. |
| | 199 | // |
| | 200 | // So warm on the isolate's FIRST fetch, in the background: MCP traffic then |
| | 201 | // pre-warms an isolate before a page request reaches it. This is #1628, which |
| | 202 | // worked; it was reverted (#1634) when memory-limit kills jumped, but that |
| | 203 | // landed during the 2026-08-17 storm — 11.67M requests in a day against a |
| | 204 | // 2.6-4.3M baseline. Volume is back to baseline and `exceededMemory` is |
| | 205 | // currently zero, so the condition that made it expensive is gone. |
| | 206 | // `START_GRAPH_WARM=false` disables it without a deploy. |
| | 207 | // |
| | 208 | // Deliberately NOT at module scope: a full warmup there trips workerd's |
| | 209 | // global-scope I/O restriction, and DO-only isolates should not carry the SSR |
| | 210 | // graph. |
| | 211 | // --------------------------------------------------------------------------- |
| | 212 | |
| | 213 | let startGraphWarmupStarted = false; |
| | 214 | |
| | 215 | const warmStartGraph = (env: Env): void => { |
| | 216 | if (startGraphWarmupStarted) return; |
| | 217 | if (env.START_GRAPH_WARM === "false") return; |
| | 218 | startGraphWarmupStarted = true; |
| | 219 | // oxlint-disable-next-line executor/no-promise-catch -- adapter boundary; fire-and-forget warmup outside any Effect runtime |
| | 220 | void Promise.all([import("#tanstack-router-entry"), import("#tanstack-start-entry")]).catch( |
| | 221 | () => { |
| | 222 | // Advisory only — the request path still loads the graph lazily. |
| | 223 | startGraphWarmupStarted = false; |
| | 224 | }, |
| | 225 | ); |
| | 226 | }; |
| | 227 | |
| 181 | const cloudflareHandler: ExportedHandler<Env> = { | 228 | const cloudflareHandler: ExportedHandler<Env> = { |
| 182 | fetch: async (request, env, ctx) => { | 229 | fetch: async (request, env, ctx) => { |
| | 230 | // First fetch in this isolate kicks the graph load off in the background, |
| | 231 | // including for /mcp — MCP traffic is what reaches these isolates first. |
| | 232 | warmStartGraph(env); |
| | 233 | |
| 183 | // Public pages must not enter TanStack Start: its first-request dynamic | 234 | // Public pages must not enter TanStack Start: its first-request dynamic |
| 184 | // import loads the entire React + Effect server graph and can take seconds | 235 | // import loads the entire React + Effect server graph and can take seconds |
| 185 | // on a cold isolate. Classify and service-bind marketing at the Worker | 236 | // on a cold isolate. Classify and service-bind marketing at the Worker |