Knowledge > Runbooks > Technical Ops > Investigate and Resolve Slow Database Queries
Investigate and Resolve Slow Database Queries
Identify the root cause of slow Supabase queries and apply the correct fix without introducing new problems.
Prerequisites
- Access to Supabase SQL editor (
https://supabase.com/dashboard/project/wrwkszmobuhvcfjipasi/sql) - Access to Vercel logs (
vercel logs --tailor Vercel dashboard) - The slow query or route identified (from error reports, user complaint, or monitoring)
Steps
-
Identify the slow query
Check each source in order:
ops_error_reportstable for timeout or performance errors:SELECT route, message, created_at FROM ops_error_reportsWHERE message ILIKE '%timeout%' OR message ILIKE '%slow%'ORDER BY created_at DESC LIMIT 20;- Vercel function logs:
vercel logs --tail(filter for the suspected route) - Supabase Dashboard → Database → Query Performance (shows slow queries)
-
Run EXPLAIN ANALYZE on the query
Wrap the suspect query with
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT):EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)SELECT * FROM churchesWHERE slug = 'grace-community-church'LIMIT 1;Look for:
Seq Scan(bad on large tables), highactual time, largerowsestimates. -
Check for the most common issues
Missing
directory_visible=truefilter onchurches:-- BAD — scans 261K rowsSELECT * FROM churches WHERE slug = 'example-church';-- GOOD — scans 218K visible rows, can use partial indexSELECT * FROM churches WHERE slug = 'example-church' AND directory_visible = true;Missing pagination on large tables:
-- BAD — may return or process thousands of rowsSELECT * FROM unified_rag_content WHERE church_id = '...';-- GOOD — paginate with .range()SELECT * FROM unified_rag_content WHERE church_id = '...'ORDER BY created_at DESCLIMIT 1000 OFFSET 0;Missing indexes on common lookup columns:
-- Check existing indexes on a tableSELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'churches';-- Common columns that should have indexes:-- churches: slug, directory_visible, denomination_id-- unified_rag_content: church_id, content_type, embedding (vector index)-- premium_churches: church_id, stripe_subscription_id-- voice_call_logs: church_id, created_at -
Add a missing index
Always use
CONCURRENTLYto avoid locking the table during index creation:CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_churches_slug_visibleON churches (slug)WHERE directory_visible = true;For
unified_rag_contentfull-text or vector queries, check if a vector index exists:SELECT indexname FROM pg_indexesWHERE tablename = 'unified_rag_content' AND indexname LIKE '%embed%'; -
Verify query time improvement
Re-run
EXPLAIN ANALYZEafter adding the index:EXPLAIN (ANALYZE, BUFFERS)SELECT * FROM churchesWHERE slug = 'grace-community-church' AND directory_visible = true;Confirm:
Index ScanreplacesSeq Scan,actual timedrops significantly. -
Fix the application query if needed
If the slow query comes from application code, locate it in the codebase:
C:\dev\churchwiseai-web\src\ (ChurchWiseAI routes)C:\dev\pewsearch\web\src\ (PewSearch routes)C:\dev\sermon-illustrations\src\ (ITW routes)Update the query to include the correct filter, pagination, or index hint.
-
Commit and deploy the fix
For index-only fixes: commit the migration file (see supabase-migration.md). For code fixes: commit to a feature branch and deploy via standard process.
Verification
EXPLAIN ANALYZEshowsIndex ScannotSeq Scanon the target table- Route response time drops to under 500ms (check Vercel function duration)
- No new entries in
ops_error_reportsfor the affected route