Unified · Split
| @@ -243,19 +243,64 @@ const markStartGraphEntered = (): void => { · 242 unmodified lines | |||
| 243 | // `servedByAppPlane` (./app-paths) decides which paths qualify — two under | 243 | // `servedByAppPlane` (./app-paths) decides which paths qualify — two under |
| 244 | // `/api` are claimed by Start's middleware first and must keep their old route. | 244 | // `/api` are claimed by Start's middleware first and must keep their old route. |
| 245 | 245 | ||
| 246 | // Instantiated on the first request that needs it and memoized per isolate, | 246 | // Instantiated once per isolate and memoized as a promise, mirroring |
| 247 | // mirroring `start.ts`'s `getApp`. The import stays dynamic so an isolate that | 247 | // `start.ts`'s `getApp`. The import stays dynamic so the Worker's static |
| 248 | // only serves pages or proxies never evaluates the app graph at all. | 248 | // startup closure does not include the app graph; the promise memo means a |
| 249 | let appPlane: ReturnType<typeof import("./app").cloudApiHandler> | undefined; | 249 | // pre-warm and a real request racing on a fresh isolate share one import. |
| 250 | type AppPlane = ReturnType<typeof import("./app").cloudApiHandler>; | ||
| 251 | let appPlanePromise: Promise<AppPlane> | undefined; | ||
| 250 | let appGraphEntered = false; | 252 | let appGraphEntered = false; |
| 251 | 253 | ||
| 252 | const getAppPlane = async (): Promise<NonNullable<typeof appPlane>> => { | 254 | const getAppPlane = (): Promise<AppPlane> => { |
| 253 | if (appPlane === undefined) { | 255 | if (appPlanePromise === undefined) { |
| 254 | const { cloudApiHandler } = await import("./app"); | 256 | appPlanePromise = import("./app").then( |
| 255 | appPlane = cloudApiHandler(); | 257 | ({ cloudApiHandler }) => { |
| 256 | appGraphEntered = true; | 258 | const plane = cloudApiHandler(); |
| 259 | appGraphEntered = true; | ||
| 260 | return plane; | ||
| 261 | }, | ||
| 262 | (cause: unknown) => { | ||
| 263 | // Do not memoize a failure: the next request re-imports, as the | ||
| 264 | // un-memoized version did, instead of failing every request after. | ||
| 265 | appPlanePromise = undefined; | ||
| 266 | // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: re-raise the import failure to the awaiting request | ||
| 267 | throw cause; | ||
| 268 | }, | ||
| 269 | ); | ||
| 257 | } | 270 | } |
| 258 | return appPlane; | 271 | return appPlanePromise; |
| 272 | }; | ||
| 273 | |||
| 274 | // --------------------------------------------------------------------------- | ||
| 275 | // Pre-warming the app plane. | ||
| 276 | // --------------------------------------------------------------------------- | ||
| 277 | // | ||
| 278 | // Measured on production 2026-09-18: 30% of `/api/*` dispatches landed on an | ||
| 279 | // isolate that had not yet evaluated the app graph, and paid ~2s (p50) for it | ||
| 280 | // against ~100ms warm. Only 43% of those isolates were under 5s old. The rest | ||
| 281 | // had been alive for seconds to minutes serving `/mcp`, discovery documents, | ||
| 282 | // or proxies, none of which enter the app graph, so the dashboard's first | ||
| 283 | // call was the one that paid. Isolates live about a minute at the median, so | ||
| 284 | // there is rarely a second dashboard request to benefit. | ||
| 285 | // | ||
| 286 | // So any request that does NOT need the app plane starts its import in the | ||
| 287 | // background. The request itself returns as before; the import runs under | ||
| 288 | // `waitUntil`, so the isolate stays up until it finishes. A dashboard call | ||
| 289 | // arriving afterwards finds the graph evaluated. The truly fresh isolate | ||
| 290 | // (first request IS a dashboard call) still pays; that cost is the graph's | ||
| 291 | // evaluation itself, addressed separately. | ||
| 292 | // | ||
| 293 | // Failure is swallowed on purpose: a pre-warm that fails must not fail the | ||
| 294 | // request that triggered it, and the next real app-plane request re-imports | ||
| 295 | // through the same memo and surfaces the error where it belongs. | ||
| 296 | const prewarmAppPlane = (ctx: ExecutionContext): void => { | ||
| 297 | if (appGraphEntered) return; | ||
| 298 | ctx.waitUntil( | ||
| 299 | getAppPlane().then( | ||
| 300 | () => undefined, | ||
| 301 | () => undefined, | ||
| 302 | ), | ||
| 303 | ); | ||
| 259 | }; | 304 | }; |
| 260 | 305 | ||
| 261 | const cloudflareHandler: ExportedHandler<Env> = { | 306 | const cloudflareHandler: ExportedHandler<Env> = { |
| @@ -266,6 +311,12 @@ const cloudflareHandler: ExportedHandler<Env> = { · 4 unmodified lines | |||
| 266 | // import loads the entire React + Effect server graph and can take seconds | 311 | // import loads the entire React + Effect server graph and can take seconds |
| 267 | // on a cold isolate. Classify and service-bind marketing at the Worker | 312 | // on a cold isolate. Classify and service-bind marketing at the Worker |
| 268 | // entry, before telemetry or fetchHandler touches that graph. | 313 | // entry, before telemetry or fetchHandler touches that graph. |
| 314 | // Everything that returns before the app-plane dispatch below leaves the | ||
| 315 | // graph unevaluated for the next request; warm it in the background. | ||
| 316 | if (!servedByAppPlane(new URL(request.url).pathname, request.method)) { | ||
| 317 | prewarmAppPlane(ctx); | ||
| 318 | } | ||
| 319 | |||
| 269 | const marketingRequest = marketingProxyRequest(request); | 320 | const marketingRequest = marketingProxyRequest(request); |
| 270 | const marketing: Fetcher | undefined = env.MARKETING; | 321 | const marketing: Fetcher | undefined = env.MARKETING; |
| 271 | if (marketingRequest && marketing) return marketing.fetch(marketingRequest); | 322 | if (marketingRequest && marketing) return marketing.fetch(marketingRequest); |
| @@ -445,6 +496,9 @@ const cloudflareHandler: ExportedHandler<Env> = { · 173 unmodified lines | |||
| 445 | // isolate goes idle. | 496 | // isolate goes idle. |
| 446 | scheduled: async (_controller, _env, ctx) => { | 497 | scheduled: async (_controller, _env, ctx) => { |
| 447 | installTracerProvider(); | 498 | installTracerProvider(); |
| 499 | // The cron fires every minute, often on an isolate that has served no | ||
| 500 | // dashboard request yet: the cheapest pre-warm there is. | ||
| 501 | prewarmAppPlane(ctx); | ||
| 448 | await runWorkOsEventsSync(); | 502 | await runWorkOsEventsSync(); |
| 449 | ctx.waitUntil(flushTracerProvider()); | 503 | ctx.waitUntil(flushTracerProvider()); |
| 450 | }, | 504 | }, |