Summary
The web UI in apps/host-cloudflare (the executor-cloudflare Worker) takes several seconds to display data on pages like /<org>/policies and /<org>/integrations/<slug>. Investigation with Workers Observability + a real browser waterfall shows the slowness is not the JS bundle or render, but a combination of (1) a large fan-out of per-request API calls that each rebuild a scoped executor, (2) an unnecessarily heavy /api/tools path, and (3) geographic distance (KR users served from a US colo).
Evidence (measured)
Browser waterfall, warm load of the integrations page:
- Document + JS bundle is fast:
domContentLoaded/load ≈ 388ms, chunks ~241ms (cached). Not the bottleneck.
- After mount the page fires ~8 parallel API calls (
account/me, integrations, tools, policies, connections ×3, openapi/.../config).
- Per-call TTFB (warm): 433–580ms for most,
/api/tools = 1168ms (the long pole). Data-complete ≈ 1.77s warm, worse cold (server-side 0.5–1.7s per call).
Geography (Workers Observability, last ~3.5h, 182 /api/* requests):
cf.country = KR, cf.colo = LAX for 100% of requests — Korean users are served from a US-West colo.
- This matches the gap between server-side span (~145ms for
/api/policies) and browser TTFB (~433ms): ~290ms is the KR↔LAX round trip.
CPU vs wall: median CPU ~40–60ms while wall time is seconds → the worker is waiting (I/O / network / round trips), not computing.
Root Causes (with code references)
/api/tools long pole. toolsList runs syncStaleConnectionTools on every list call, which issues 2 unconditional D1 queries even when nothing changed (packages/core/sdk/src/executor.ts:2475 call, :2438 definition). It also calls listOperations, which reads the entire plugin_storage operation set and filters in JS (packages/plugins/openapi/src/sdk/store.ts:150-156). D1 caps bound params at 100, so wide reads batch (apps/host-cloudflare/src/db/d1.ts).
- Page fan-out rebuilds a scoped executor per call. Each of the ~8 calls independently builds a scoped executor and re-reads the catalog/policies (
packages/core/api/src/server/scoped-executor.ts:253). The D1 handle itself is shared and built once per isolate (apps/host-cloudflare/src/app.ts), and auth (Cloudflare Access JWT) is cheap and JWKS-cached (apps/host-cloudflare/src/auth/cloudflare-access.ts:73) — so the redundancy is the executor build + repeated D1 reads, multiplied by 8.
- Geographic placement. No placement config; KR users are served from LAX (~290ms RTT). Paid ~once per page because calls are parallel, but still adds to every page.
Proposed Fixes (impact order)
Notes
- Measurements taken 2026-06-25 via Workers Observability and a browser waterfall.
- Recommended order: A, then B, then C; D and E are separate tracks.
- To see the specific tasks where the Asana app for GitHub is being used, see below:
Summary
The web UI in
apps/host-cloudflare(theexecutor-cloudflareWorker) takes several seconds to display data on pages like/<org>/policiesand/<org>/integrations/<slug>. Investigation with Workers Observability + a real browser waterfall shows the slowness is not the JS bundle or render, but a combination of (1) a large fan-out of per-request API calls that each rebuild a scoped executor, (2) an unnecessarily heavy/api/toolspath, and (3) geographic distance (KR users served from a US colo).Evidence (measured)
Browser waterfall, warm load of the integrations page:
domContentLoaded/load≈ 388ms, chunks ~241ms (cached). Not the bottleneck.account/me,integrations,tools,policies,connections×3,openapi/.../config)./api/tools= 1168ms (the long pole). Data-complete ≈ 1.77s warm, worse cold (server-side 0.5–1.7s per call).Geography (Workers Observability, last ~3.5h, 182
/api/*requests):cf.country = KR,cf.colo = LAXfor 100% of requests — Korean users are served from a US-West colo./api/policies) and browser TTFB (~433ms): ~290ms is the KR↔LAX round trip.CPU vs wall: median CPU ~40–60ms while wall time is seconds → the worker is waiting (I/O / network / round trips), not computing.
Root Causes (with code references)
/api/toolslong pole.toolsListrunssyncStaleConnectionToolson every list call, which issues 2 unconditional D1 queries even when nothing changed (packages/core/sdk/src/executor.ts:2475call,:2438definition). It also callslistOperations, which reads the entireplugin_storageoperation set and filters in JS (packages/plugins/openapi/src/sdk/store.ts:150-156). D1 caps bound params at 100, so wide reads batch (apps/host-cloudflare/src/db/d1.ts).packages/core/api/src/server/scoped-executor.ts:253). The D1 handle itself is shared and built once per isolate (apps/host-cloudflare/src/app.ts), and auth (Cloudflare Access JWT) is cheap and JWKS-cached (apps/host-cloudflare/src/auth/cloudflare-access.ts:73) — so the redundancy is the executor build + repeated D1 reads, multiplied by 8.Proposed Fixes (impact order)
/api/toolsserver time (long pole). SkipsyncStaleConnectionToolson the read path when no integration has been revised (cache the check, or move sync to write-time / a background task). MakelistOperationsfilter by integration at the DB layer instead of scanning all rows.scoped-executor.ts:253).*.workers.devsubdomain routing; test a zone custom domain). If the worker is moved closer to users, D1 must also get a nearby read replica (D1 read replication / Sessions API), otherwise multi-query endpoints get slower.apps/host-cloudflare/src/mcp/session-durable-object.ts). Unrelated to page latency; review for cost/hibernation.Notes