Skip to content

Cold Start & Performance Optimization

Overall performance has degraded across all apps and services. Users experience slow page loads, particularly on initial visits. The webapp landing page takes ~3.5-5 seconds on cold start.

EndpointRun 1Run 2Run 3Notes
Landing Page261ms128ms111msFirst run slowest (warm server)
Health Check259ms221ms263msConsistent ~250ms
Dashboard (unauth)234ms113ms143msRedirects to sign-in
Sign In Page129ms119ms117msConsistent ~120ms

Client-Side Timing (Playwright browser, full page load)

Section titled “Client-Side Timing (Playwright browser, full page load)”
EndpointDurationStatusNotes
Landing Page3,502ms200Includes CSS, JS, fonts, hydration
Sign In Page1,834ms200Clerk widget loads
Health Check API256ms200JSON only

Traffic Generator (Node.js fetch, sequential)

Section titled “Traffic Generator (Node.js fetch, sequential)”
EndpointDurationStatus
Health Check314ms200
Landing Page282ms200
Sign In181ms200
Dashboard (unauth)159-227ms200
Orders (unauth)154-197ms200
Auth Check76-163ms200

Average latency (all endpoints): 145ms Cold start (first browser visit): ~3,500-5,000ms

The cold start is NOT an Astro problem. It’s a startup initialization cascade:

1. OpenTelemetry Full Auto-Instrumentation (~1-2s)

Section titled “1. OpenTelemetry Full Auto-Instrumentation (~1-2s)”

otel-init.cjs runs via --require before Astro starts and calls getNodeAutoInstrumentations() which monkey-patches 20+ Node.js modules. This is the single biggest contributor.

Current config:

// otel-init.cjs line 18
const ENABLE_AUTO_INSTRUMENTATION = process.env.OTEL_AUTO_INSTRUMENTATION !== 'false';
// Defaults to FULL mode (20+ modules) since env var is not set

Fix: Set OTEL_AUTO_INSTRUMENTATION=false → reduces to HTTP-only instrumentation.

2. Flagsmith Local Evaluation Init (~1-3s on first request)

Section titled “2. Flagsmith Local Evaluation Init (~1-3s on first request)”

Feature flags use local evaluation which downloads the entire Flagsmith environment document on the first request. requestTimeoutSeconds: 30 means it can block for up to 30 seconds.

Fix: Reduce timeout to 5s, eager-initialize at server startup.

3. Pino Logger Dynamic Import (~200-500ms)

Section titled “3. Pino Logger Dynamic Import (~200-500ms)”

loadPinoLogger uses await import('pino') lazily on first log call.

4. Clerk Auth JWKS Fetch (~200-500ms on first request)

Section titled “4. Clerk Auth JWKS Fetch (~200-500ms on first request)”

Clerk fetches JSON Web Key Set on the first authenticated request.

5. Neon DB WebSocket Connection (~200-500ms on first query)

Section titled “5. Neon DB WebSocket Connection (~200-500ms on first query)”

Neon serverless driver establishes WebSocket on first query.

File: infrastructure/coolify/scripts/set-environment-staging.sh, set-environment.sh Change: Added OTEL_AUTO_INSTRUMENTATION=false to webapp env vars What it does: Switches from getNodeAutoInstrumentations() (20+ module patches) to HTTP-only instrumentation Expected impact: ~1-2 seconds off cold start

Fix 2: Eager Flagsmith Init + Reduced Timeout

Section titled “Fix 2: Eager Flagsmith Init + Reduced Timeout”

File: apps/webapp/src/middleware/feature-flags.ts Changes:

  • Added setTimeout(() => ensureInitialized(), 0) at module import to trigger init at server startup
  • Reduced requestTimeoutSeconds from 30 to 5 (fail fast, fall back to API mode)
  • Reduced retries from 2 to 1 (avoid blocking startup) Expected impact: ~1-3 seconds off first-request latency (moved to startup)

File: packages/db/src/client.ts Change: Added pool.query('SELECT 1') at module load to establish WebSocket connection at startup Expected impact: ~200-500ms off first query

  • Cold start (browser): ~3,500ms landing page, ~1,834ms sign-in
  • Warm (curl TTFB): ~110-260ms
  • Warm (fetch): ~145ms average

After All Fixes (Post-Deploy: 2026-02-07T14:17Z)

Section titled “After All Fixes (Post-Deploy: 2026-02-07T14:17Z)”

Cold start (first request to freshly started container):

  • Landing page: 10.5s (container boot + Node.js startup + OTEL init + Flagsmith init + DB connect)
  • This is the Docker container starting from scratch — users don’t see this unless container restarts

Warm state (TTFB, curl, 5 samples each):

EndpointRun 1Run 2Run 3Run 4Run 5Avg
Landing113ms118ms204ms241ms185ms172ms
Health152ms111ms183ms98ms92ms127ms
Dashboard118ms197ms110ms100ms210ms147ms
Sign-in102ms112ms112ms--109ms
Orders164ms114ms109ms--129ms

Health check noise reduction:

  • Before: ~227 logs per 30 minutes
  • After: 8 logs per 30 minutes
  • 96.5% noise reduction

Cold Start Analysis:

The 10.5s cold start is the Docker container boot, not app initialization. Our fixes moved initialization costs from “first user request” to “server startup”:

  1. Container boot + Node.js init: ~2-3s
  2. OTEL init (now HTTP-only): ~0.5-1s (was ~1-2s with full auto-instrumentation)
  3. Astro SSR server startup: ~1-2s
  4. Flagsmith eager init (async, 5s timeout): runs during startup, not first request
  5. DB pre-warm SELECT 1: runs during startup, not first query

Users won’t hit this unless the container restarts. Coolify health checks gate traffic.

  • DB health check filter: ✅ Working (96.5% noise reduction)
  • Webapp traces: ❌ Pending — need to run ./set-environment-staging.sh for OTEL env vars
  • Webapp logs: ❌ Pending — need to run ./set-environment-staging.sh for LOGTAIL env vars
  • Note: Code is deployed but Coolify env vars haven’t been updated yet
  • Working correctly: 17,827 total traces
  • Recent trace latencies: 7.5s - 63.6s
  • Costs: $0.0009 - $0.0613 per trace
  • Correlation IDs flowing correctly

E2E Performance Baseline (Authenticated, Playwright, 2026-02-07T14:45Z)

Section titled “E2E Performance Baseline (Authenticated, Playwright, 2026-02-07T14:45Z)”

Full User Journey (41 measurements, 1.1 minute total)

Section titled “Full User Journey (41 measurements, 1.1 minute total)”

Top 5 Bottlenecks:

RankStepDurationCategoryNotes
1Clerk sign-in redirect16,149msinteractionClient-side JS redirect to accounts.dev
2Feature Flags API8,486msapi/api/admin/feature-flags — extremely slow
3Auth redirect → dashboard4,066msinteractionClerk callback → session verify → redirect
4Landing page2,388msnavigationFull browser load with CSS/JS/fonts
5Sign-in page2,014msnavigationFull browser load

Authenticated Page Loads (SSR, browser networkidle)

Section titled “Authenticated Page Loads (SSR, browser networkidle)”
PageDurationNotes
Dashboard1,294-1,519msConsistent ~1.3-1.5s
Orders1,409-1,680msConsistent ~1.4-1.7s
Inbox1,186-1,246msConsistent ~1.2s
ERP Connections1,165ms404 (no connections)
Organization1,171ms
Admin1,184ms
Admin Billing1,445ms

Average authenticated page load: 1,328ms (p50), 1,424ms (avg)

API Endpoints (in-browser fetch with cookies)

Section titled “API Endpoints (in-browser fetch with cookies)”
EndpointDurationStatusNotes
/api/admin/feature-flags8,486ms200Critical bottleneck
/api/auth/check (warm)691ms200Clerk session verify
/api/pdf-documents526-634ms200DB query + serialization
/api/billing/pricing511ms500Error response
/api/billing/subscription-limits489ms200
/api/auth/check350ms200
/api/auth/me319ms200
/api/billing/usage/current-period303ms200
/api/auth/organization284ms200
/api/auth/session255ms200
/api/admin/system-stats232ms200
/api/erp/connections203-226ms200Fast
/api/inbox/check-updates173ms404
/api/health151ms200Baseline
  1. Feature Flags API is the #1 bottleneck (8.5s) — The /api/admin/feature-flags endpoint takes 8.5 seconds. This likely does a full Flagsmith environment download or has an inefficient implementation. Needs immediate investigation.

  2. Authenticated pages consistently take ~1.2-1.7s in the browser. Server-side traces show only ~20-35ms of server processing. The gap (~1.1-1.6s) is:

    • Network round-trip to staging server (~100ms)
    • TLS handshake (~40ms)
    • Clerk session verification (~200-350ms per request)
    • Browser HTML parsing + CSS/JS download + React hydration (~500-800ms)
  3. PDF Documents API is slow (500-634ms) — likely a DB query that could be optimized.

  4. Billing Pricing returns 500 — broken endpoint, needs fixing.

  5. Auth Check varies significantly (350ms cold → 691ms warm) — unexpected that it is slower when warm. Could be Clerk rate limiting or session cache invalidation.

Auth Performance Fixes Applied (Post-Baseline)

Section titled “Auth Performance Fixes Applied (Post-Baseline)”

File: packages/auth/src/middleware.ts Change: Added early return before ANY Clerk API calls for public routes that don’t need org context. Previously, authenticateRequest() ran on every single request when enableOrganizationContext was true — even for /, /sign-in, etc. Expected impact: ~200-600ms saved for public page loads

File: packages/auth/src/server.ts Change: authenticateRequest() now tries verifyToken() first, which validates the JWT signature against cached JWKS locally — no network call to Clerk. Falls back to the remote authenticateRequest() only if local verification fails (expired token, etc.). Expected impact: ~100-300ms saved per authenticated request (eliminates JWKS fetch)

File: packages/auth/src/server.ts Change: getUserFromAuth() and getUserFromSession() now check Redis before calling Clerk’s users.getUser() API. 5-minute TTL. Cache key: auth:user:{userId}. Expected impact: ~100-300ms saved per authenticated request (eliminates user fetch)

  • Public pages: from ~2-2.6s → ~1-1.5s (skip auth entirely)
  • Authenticated pages: from ~1.2-1.7s → ~0.7-1.0s (local JWT + cached user)
  • API endpoints: from ~150-700ms → ~100-300ms (local JWT + cached user)

Post-Fix Results (Deployed 2026-02-07T17:00Z)

Section titled “Post-Fix Results (Deployed 2026-02-07T17:00Z)”

API Endpoints (authenticated, in-browser):

EndpointBaselineAfter All FixesImprovement
Feature Flags8,486ms90ms99% faster (94x)
Auth Me319ms95ms70% faster
Auth Session255ms81ms68% faster
Auth Organization284ms98ms65% faster
Auth Check (warm)691ms246ms64% faster
Health151ms69ms54% faster
ERP Connections203ms123ms39% faster
PDF Documents634ms390ms38% faster
API Average865ms181ms79% faster

Page Loads (browser, authenticated):

PageBaselineAfterImprovement
Organization1,171ms874ms25% faster
Dashboard1,329ms1,048ms21% faster
Admin1,184ms966ms18% faster
Page Average1,424ms1,195ms16% faster

Fix 7: Feature Flags Admin Endpoint (Fix #1 Bottleneck)

Section titled “Fix 7: Feature Flags Admin Endpoint (Fix #1 Bottleneck)”

File: apps/webapp/src/pages/api/admin/feature-flags/index.ts Change: Replaced two sequential fetch() calls (no timeout, ~9-13s) with SDK getAllFlags() which uses local evaluation (in-memory cache, ~0ms). Result: 8,486ms → 90ms (99% faster)

File: apps/webapp/src/pages/api/admin/feature-flags/toggle.ts Change: Added 5s AbortController timeout to prevent hanging.

E2E Pipeline Timing (Upload → Process → Review)

Section titled “E2E Pipeline Timing (Upload → Process → Review)”
StepDurationNotes
Upload to R20.7sPresigned URL + R2 upload + document creation
PDF Processing13.8spdf-api → pdf-worker → Gemini extraction
Customer search0.9sTypesense search + modal interaction
Shipping search0.8sTypesense search + modal interaction
Auto-validate0.1sERP parts lookup
Total pipeline16.3sUpload through review interactions
Document TypeChunksGemini CallTotalCost
Simple (3 items)14.5s~14s (incl. polling)$0.0003
Complex (multi-page)916-24s each~33s (5-way parallel)$0.0143

Priority 1: Increased PDF_PAGES_PER_CHUNK from 2 → 4

  • A 10-page PDF now produces 3 chunks instead of 9
  • With 8-way parallelism, all chunks run concurrently
  • Expected: ~50% faster on large documents

Priority 2: Adaptive polling in DocumentOrderReview.tsx

  • Changed from fixed 3s interval to adaptive: 1s for first 30s, then 3s
  • Reduces perceived latency by ~2s when processing completes quickly
  1. Native Gemini PDF handling — Send PDF directly to Gemini instead of text chunks (8-12h effort)
  2. Reduce prompt size — 2,135 tokens per chunk, could be trimmed (2-4h effort)
  3. Batch upload UX — Multi-file upload with inline queue (4-6h, plan at debt/projects/ux-improvements/batch-upload-and-extraction-plan.md)
  4. SSE for status updates — Replace polling with server-sent events (4-6h)
  5. Fix Billing Pricing 500 — Broken endpoint returning errors
  6. Optimize client-side hydration — Browser JS execution is ~500-800ms per page
  • scripts/staging-traffic-generator.ts — Quick fetch-based traffic gen
  • scripts/generate-test-pdf.ts — Generate test PDF with real ERP data from Typesense
  • apps/webapp/playwright-demos/staging-traffic.spec.ts — Playwright browser traffic
  • apps/webapp/playwright-demos/e2e-staging-perf.spec.ts — Full authenticated E2E perf audit
  • apps/webapp/playwright-demos/e2e-pdf-pipeline.spec.ts — Full PDF upload → process → review test
  • apps/webapp/playwright-demos/e2e-perf-audit.spec.ts — CDP-based E2E audit (requires Chrome remote debugging)