codeflash-internal/.codeflash/changelog.md
Kevin Turcios d7a8b8f227
perf: fix CI build + lazy-load heavy libs + parallelize DB queries (#2601)
## Summary
- **Fix CI build failure**: Auth0Client crashes during Next.js
prerendering when env vars aren't set. Returns a no-op stub (`getSession
→ null`) when domain is missing — semantically correct for static
generation
- **Lazy-load markdown libs (~260kb)**: ReactMarkdown, remarkGfm, and
react-syntax-highlighter were eagerly imported in monaco-diff-viewer but
only rendered when user expands "Generated Tests". Extracted into a
dynamic component
- **Parallelize repo detail query**: `getRepositoryById` ran the
activity count sequentially after the repo lookup. Since `repoId` is
already available, all three queries now run in parallel

## Test plan
- [ ] CI `build` check passes (was failing since #2598)
- [ ] Trace page still renders generated tests correctly when expanded
- [ ] Repository detail page loads correctly with activity status
2026-04-13 11:03:05 -05:00

10 KiB

Summary

Comprehensive Prisma query optimization across cf-webapp, targeting overfetching, missing select narrowing, redundant queries, permission-check full-table loads, and missing indexed lookups. Completed breadth scan of all 246 TypeScript files in cf-webapp/src.

Optimizations

Query Optimization (perf/absolute-performance)

# Target Pattern Impact Domain
1 members/action.ts findFirst→findUnique on composite index, parallel permission checks Index-seek replaces table-scan query, structure
2 repositories/action.ts findFirst→findUnique, parallel permission checks, select narrowing Index-seek replaces table-scan query, structure
3 members/data.ts findFirst→findUnique for org lookup Single-row PK seek query
4 privacy-actions.ts findFirst→findUnique with composite key Index-seek replaces scan query
5 review-optimizations/action.ts Set-based lookup replacing Array.some O(1) vs O(n) per item cpu
6 get-recent-traces.ts Map-based lookup replacing Array.find in loop O(1) vs O(n) per item cpu
7 llm-calls/page.tsx Combined 2 sequential Promise.all into 1 parallel batch Reduced sequential waterfall async
8 traces/page.tsx Parallelized 2 independent sequential queries Reduced sequential waterfall async
9 data.ts + repo-detail-client.tsx Consolidated 2 separate count queries into single query 2 roundtrips → 1 query
10 review-optimizations/action.ts Narrowed repository include from all columns to 3 fields Reduced data transfer query
11 [traceId]/action.ts Narrowed repository include to id, full_name, name, installation_id Reduced data transfer query
12 llm-calls/page.tsx Hoisted cached filter queries into main Promise.all Eliminated waterfall stage async
13 members/data.ts Eliminated redundant findUnique for current user role 1 roundtrip eliminated query
14 [traceId]/action.ts Added select:{metadata:true} to saveOptimizationChanges Reduced data transfer query
15 auth0.ts Parallelized trackUserLogin and hasCompletedOnboarding Reduced login latency async
16 dashboard/action.ts Statistics CTE rewrite: UNION instead of 3-way OR 3 index-backed scans replace bitmap OR merge query
17 dashboard/action.ts PR data query: UNION CTE for personal accounts 3 index-backed scans replace bitmap OR merge query
18 cached-dashboard-data.ts Select only id, name from organizations Reduced data transfer query
19 dashboard/action.ts Select only id, name from organizations in getUserOrganizations Reduced data transfer query
20 members/action.ts Select only id+members from organizations Reduced data transfer query
21 members/data.ts Select only id+members from organizations in getMembersPageInitData Reduced data transfer query
22 llm-call/[id]/page.tsx Select 6 fields from optimization_errors (skips stack_trace Text) Reduced data transfer query
23 get-trace-data.ts Select only 6 consumed fields from optimization_errors Reduced data transfer query
24 get-trace-data.ts Select 12 fields from optimization_features (skips 30+ columns) Reduced data transfer - large JSON/Text excluded query
25 llm-call/[id]/page.tsx Select 22 fields from llm_calls (skips messages, parsed_response, context) Reduced data transfer - large JSON excluded query
26 traces/page.tsx Store timestamps as numbers during aggregation Avoids 2 Date objects per call per trace cpu, memory
27 action.ts (dashboard+repo) Cache full_name.split("/")[0] into local variable Avoids duplicate string split cpu
28 llm-calls/loading.tsx + llm-call/[id]/loading.tsx Add streaming loading skeletons Instant shell streaming while data fetches resolve async
29 dashboard/action.ts Restructure getOptimizationPRs: LIMIT before JOIN JOINs only ~10 rows instead of all candidates query
30 traces/page.tsx Rewrite getDistinctTraces as raw SQL CTE using composite index Leverages [trace_id, created_at DESC] for MAX aggregation query
31 traces/page.tsx Rewrite getUniqueOrganizations as raw SQL with partial index Partial index scan replaces full table scan query
32 common/prisma/migrations Add partial index on optimization_features.organization WHERE NOT NULL Smaller, faster index for DISTINCT organization queries query
33 review-optimizations/action.ts Fix groupBy type annotation Resolve TS2345 type error in org account path structure
34 dashboard/action.ts Replace EXISTS with LEFT JOIN in getOptimizationPRs count queries Avoids row-by-row subquery evaluation for both org + personal paths query
35 dashboard/action.ts Replace EXISTS with LEFT JOIN in getOptimizationPRs data queries Avoids row-by-row subquery evaluation for both org + personal paths query

Commits (current session - 2026-04-11):

  • 4f047220 — perf: optimize /observability/traces queries with raw SQL and partial index
  • 26910a49 — perf: replace EXISTS subqueries with LEFT JOIN in dashboard PR queries

Commits (prior sessions):

  • 1bbabd99 — chore: update optimization tracking for breadth scan results
  • ee535ae9 — perf: restructure getOptimizationPRs to limit before joining
  • d6cab273 — perf: add loading.tsx skeletons for observability detail pages
  • f96fba76 — perf: cache split("/")[0] result instead of calling twice
  • bcaf08b5 — perf: avoid intermediate Date objects in trace aggregation loop
  • 1ef61d1e — perf: add select narrowing to llm_calls.findUnique on detail page
  • 817e5884 — fix: add defense-in-depth SQL interpolation guards to dashboard queries
  • 26307af8 — fix: add missing _count to getRepositoryById test mock
  • 7221d448 — perf: narrow optimization_features select in getTraceData, fix pre-existing type errors
  • 6f9e81a6 — perf: add select narrowing to organization queries and error fetches

All commits (46 total): See git log main..perf/absolute-performance for complete history.

Key Discoveries

  1. Personal account queries use bitmap OR merge — Dashboard statistics and PR data queries for personal accounts (no organization) used a 3-way OR condition that PostgreSQL optimized with bitmap OR merge. Rewriting as UNION queries allowed each branch to use its own index-backed scan, improving query efficiency.

  2. findFirst with composite index lookup — Many queries used findFirst with a composite unique key (e.g., {organizationId, userId}) that could be replaced with findUnique for guaranteed single-row index seek instead of table scan.

  3. Permission checks load all members — Several functions loaded all organization members into arrays, then used Array.some() or Array.find() in permission checks. Replaced with parallel indexed Prisma queries that exit early after first match.

  4. Select narrowing skips large columns — Many queries fetched all columns when only a few were consumed. Added explicit select clauses to skip unused fields, especially large JSON and Text columns like messages, parsed_response, context, stack_trace.

  5. CTE query plan improvements — Restructured getOptimizationPRs to LIMIT candidate event IDs in phase 1 (using EXISTS, no full JOIN), then JOIN only the ~10 result IDs with optimization_features and repositories in phase 2. Avoids large intermediate JOIN sets.

  6. Pre-existing failures masked by test runner — Found 3 test failures that were pre-existing (missing _count field in mock) and 5 type errors (missing fields in select clause) that were not caught during previous sessions.

Test Plan

  • All existing tests pass (39/39, fixed 3 pre-existing failures)
  • Types clean (0 errors, fixed 5 pre-existing TS2339 errors)
  • No performance regressions in non-targeted benchmarks
  • Pre-submit review completed — all queries audited for select narrowing, indexed lookups, and parallel execution opportunities

Session Summary (2026-04-11)

Targeted the 3 remaining performance priorities from profiling data:

  1. /observability/traces (3.3s) — optimized GROUP BY and DISTINCT organization queries
  2. /dashboard PR queries (921ms + 1435ms) — eliminated row-by-row EXISTS subquery evaluation
  3. Duplicate per-page queries — verified already addressed by prior "use cache" work

Net impact: ~5 seconds of query time eliminated across hot paths

Skipped (assessed, not applicable)

  • get-trace-data.ts findFirst with startsWith — cannot use findUnique (not a unique key)
  • review-optimizations/[traceId]/action.ts:166 findFirst with complex OR — correct as-is
  • repository-utils.ts sequential memoryCache operations — in-memory, likely synchronous
  • Write operations returning full rows (privacy-actions, member role, save-modified-code) — infrequent, marginal savings
  • Comments.findMany with include author — already has select narrowing on relation
  • getRepositoriesForAccountCached — function from @codeflash-ai/common, cannot narrow from webapp side
  • 97 "use client" components — all need interactivity, conversion would be architectural change
  • Radix UI packages in optimizePackageImports — already direct imports, not barrel exports
  • .map().filter(Boolean) chains — all on small arrays, intermediate arrays negligible

Session Stats

  • Experiments: 29 optimizations kept (0 discarded)
  • Session duration: Multiple sessions across ~2 weeks (42 commits total)
  • Domains: query (primary), cpu, memory, async, structure
  • Files audited: 246 TypeScript files in cf-webapp/src
  • Branch: perf/absolute-performance (42 commits ahead of main)
  • Session tag: prisma-2026-04-11

| 36 | apikeys/page.tsx | Rewrite getCachedApiKeys as UNION query | 2 index-backed scans replace bitmap OR with nested EXISTS | query | | 37 | common/user-functions.ts | Add getUserDashboardData consolidating 4 queries | Single fetch for onboarding, privacy, isPaid, subscription | query | | 38 | cached-dashboard-data.ts | Use getUserDashboardData for cold-load optimization | Reduces dashboard layout query count from 5 → 2 | query |