Skip to content

[Optimization]: Prisma Singleton + DB Connection Pooling + UX Improvements#39

Open
Eli5DeFi wants to merge 1 commit intofeature/psychic-consensus-oracle-uifrom
optimize/prisma-singleton-connection-pooling
Open

[Optimization]: Prisma Singleton + DB Connection Pooling + UX Improvements#39
Eli5DeFi wants to merge 1 commit intofeature/psychic-consensus-oracle-uifrom
optimize/prisma-singleton-connection-pooling

Conversation

@Eli5DeFi
Copy link
Owner

Summary

Critical performance & correctness fixes discovered during optimization cycle. All changes target the feature/psychic-consensus-oracle-ui base.

🔴 Critical: Connection Pool Exhaustion Fixed

Problem: 11 API routes were calling new PrismaClient() on every module load, creating independent connection pools that exhaust the database connection limit under any real load.

Fix: All routes now use the shared singleton from @/lib/prisma (which already existed but was unused). Also removed prisma.$disconnect() calls that were closing the shared pool mid-request.

Affected files (11):

  • betting/platform-stats, betting/trending, betting/recent
  • users/bets, users/performance
  • badges, badges/[userId]
  • analytics/leaderboard, analytics/stats
  • share, lib/badges

⚡ Performance

N+1 Query Eliminated (analytics/stats)

  • Before: groupBy + separate findUnique = 2 sequential DB round-trips
  • After: Single $queryRaw with JOINs (stories → chapters → choices → bets)
  • Impact: ~50% fewer DB calls for this endpoint

Leaderboard Caching Added

  • analytics/leaderboard was uncached — now has 2-minute in-memory cache
  • Matches pattern used by trending and platform-stats

🔐 Security

SQL Injection Risk Fixed (analytics/leaderboard)

  • Before: timeframeCutoff (ISO string) interpolated directly into $queryRawUnsafe; LIMIT also string-interpolated
  • After: $queryRaw tagged template with typed Date parameter; ORDER BY uses a pre-validated whitelist map
  • Impact: Parameterized queries prevent SQL injection even if validation is bypassed

🏗️ Config

Single Next.js Config File

  • Before: next.config.js (CJS, optimized) + next.config.mjs (ESM, basic) — Next.js 14 picks .mjs first, so all the splitChunks/image/caching config in .js was silently ignored
  • After: Single next.config.mjs with everything merged: splitChunks, avif/webp images, security headers, Web3 webpack polyfills, bundle analyzer

⚛️ Web3 Provider

  • QueryClient moved from module scope into useState() — fixes React 18 Concurrent Mode hydration edge cases
  • wagmiConfig and rainbowTheme kept as module-level constants (correct — they don't capture component state)
  • Added defaultOptions: staleTime: 10s, skip retry on 4xx errors

🖥️ UX / Loading States

Added Next.js 13+ loading.tsx files for instant skeleton display:

  • app/dashboard/loading.tsx — DashboardStats + Chart skeletons
  • app/leaderboards/loading.tsx — LeaderboardSkeleton
  • app/story/[storyId]/loading.tsx — BettingPool + Chart + Activity skeletons

♿ Accessibility

BettingInterface improvements:

  • aria-pressed on choice selection buttons (screen readers know selection state)
  • aria-label on choice buttons (announces text + odds)
  • aria-label + aria-describedby on bet amount input
  • role="alert" + aria-live="assertive" on error messages (announced immediately)

📊 Metrics (Before → After)

Metric Before After
DB connections per burst (10 concurrent API calls) ~110 (11 × 10) 10 (shared pool)
analytics/stats DB queries 2 (N+1) 1 (JOIN)
analytics/leaderboard cache None 2-min TTL
Next.js config optimisations active ❌ (js ignored)
Loading state pages 0 3
TypeScript errors (new) 0

Testing

  • pnpm type-check passes (exit 0, zero errors)
  • All 11 files verified: no new PrismaClient(), no $disconnect()
  • Leaderboard: SQL injection fixed, caching added
  • Analytics stats: N+1 eliminated
  • Loading skeletons render correctly
  • Lighthouse score (run after merge)
  • Mobile testing

Impact

Connection stability: System won't crash under moderate load anymore. The new PrismaClient() pattern would have caused "too many connections" errors the moment 2-3 concurrent users hit different API endpoints simultaneously.

Perceived performance: Loading skeletons make all 3 heavy pages feel instant instead of showing blank content while data fetches.

SEO / Accessibility: ARIA improvements let screen readers navigate the betting interface correctly.

⚠️ DO NOT MERGE until manual review. Create from feature/psychic-consensus-oracle-ui base.

…g skeletons, ARIA

## Critical Fixes (Connection Pool)
- Replace new PrismaClient() with singleton in 11 files
  - betting/platform-stats, trending, recent
  - users/bets, users/performance
  - badges, badges/[userId]
  - analytics/leaderboard, analytics/stats
  - share, lib/badges
- Remove prisma.$disconnect() from all API routes (singleton must stay alive)
- Remove empty finally{} blocks left behind

## Performance
- analytics/stats: N+1 query → single JOIN query for most popular story
  (was: groupBy + separate findUnique = 2 DB round-trips)
  (now: 1  with JOINs)
- analytics/leaderboard: add 2-min in-memory cache (was uncached)
- analytics/leaderboard: proper parameterised timeframe cutoff via $queryRaw
  tagged template (Date param, not string interpolation)

## Security
- analytics/leaderboard: eliminate SQL injection via ORDER BY whitelist map
  (never interpolate user sortBy param directly into SQL)
- analytics/leaderboard: use $queryRaw tagged template for timeframe param
  (was string-interpolated ISO timestamp in $queryRawUnsafe)

## Config
- Merge next.config.js + next.config.mjs into single next.config.mjs
  (Next.js 14 picks .mjs first; .js was silently ignored)
  - All splitChunks, image optimisation, security headers preserved
  - Web3 webpack polyfills (fs/net/tls: false) merged in
  - stale .next cache cleared (removed stale insurance route types)

## Web3 Provider
- QueryClient moved into useState() (React 18 Concurrent Mode safe)
- wagmiConfig + rainbowTheme extracted as module-level constants (stable refs)
- React Query defaultOptions: staleTime=10s, skip retry on 4xx errors

## UX / Loading States
- Add dashboard/loading.tsx skeleton (DashboardStatsSkeleton + ChartSkeleton)
- Add leaderboards/loading.tsx skeleton
- Add story/[storyId]/loading.tsx skeleton (BettingPool + Chart + Activity)

## Accessibility
- BettingInterface: aria-pressed on choice buttons
- BettingInterface: aria-label on choice buttons (text + odds)
- BettingInterface: aria-label + aria-describedby on bet amount input
- BettingInterface: role=alert + aria-live=assertive on error message

## Code Quality
- Switch console.error → logger.error in 10 API routes
  (uses existing logger util that strips debug logs in production)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants