Unified · Split
| @@ -243,19 +243,64 @@ const markStartGraphEntered = (): void => { · 242 unmodified lines | ||
| 243 | 243 | // `servedByAppPlane` (./app-paths) decides which paths qualify — two under |
| 244 | 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, | |
| 247 | −// mirroring `start.ts`'s `getApp`. The import stays dynamic so an isolate that | |
| 248 | −// only serves pages or proxies never evaluates the app graph at all. | |
| 249 | −let appPlane: ReturnType<typeof import("./app").cloudApiHandler> | undefined; | |
| 246 | +// Instantiated once per isolate and memoized as a promise, mirroring | |
| 247 | +// `start.ts`'s `getApp`. The import stays dynamic so the Worker's static | |
| 248 | +// startup closure does not include the app graph; the promise memo means a | |
| 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 | 252 | let appGraphEntered = false; |
| 251 | 253 | |
| 252 | −const getAppPlane = async (): Promise<NonNullable<typeof appPlane>> => { | |
| 253 | − if (appPlane === undefined) { | |
| 254 | − const { cloudApiHandler } = await import("./app"); | |
| 255 | − appPlane = cloudApiHandler(); | |
| 256 | − appGraphEntered = true; | |
| 254 | +const getAppPlane = (): Promise<AppPlane> => { | |
| 255 | + if (appPlanePromise === undefined) { | |
| 256 | + appPlanePromise = import("./app").then( | |
| 257 | + ({ cloudApiHandler }) => { | |
| 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 | 306 | const cloudflareHandler: ExportedHandler<Env> = { |
| @@ -266,6 +311,12 @@ const cloudflareHandler: ExportedHandler<Env> = { · 4 unmodified lines | ||
| 266 | 311 | // import loads the entire React + Effect server graph and can take seconds |
| 267 | 312 | // on a cold isolate. Classify and service-bind marketing at the Worker |
| 268 | 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 | 320 | const marketingRequest = marketingProxyRequest(request); |
| 270 | 321 | const marketing: Fetcher | undefined = env.MARKETING; |
| 271 | 322 | if (marketingRequest && marketing) return marketing.fetch(marketingRequest); |
| @@ -445,6 +496,9 @@ const cloudflareHandler: ExportedHandler<Env> = { · 173 unmodified lines | ||
| 445 | 496 | // isolate goes idle. |
| 446 | 497 | scheduled: async (_controller, _env, ctx) => { |
| 447 | 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 | 502 | await runWorkOsEventsSync(); |
| 449 | 503 | ctx.waitUntil(flushTracerProvider()); |
| 450 | 504 | }, |