-- Database Optimization Script -- Date: January 4, 2026 -- Purpose: Remove redundant indexes, optimize queries, improve performance -- ============================================ -- STEP 1: Remove Redundant Indexes -- ============================================ -- Remove duplicate index on profile_song_keys -- Keep: uq_profile_song_key (unique constraint) -- Keep: idx_profile_song_keys (for lookups) -- Remove: idx_profile_keys (exact duplicate) DROP INDEX IF EXISTS idx_profile_keys; -- Remove redundant single-column index on plan_songs -- Keep: idx_plan_songs_order (composite index: plan_id, order_index) -- Remove: idx_plan_songs_plan (redundant - composite index handles this) DROP INDEX IF EXISTS idx_plan_songs_plan; -- ============================================ -- STEP 2: Optimize Low-Cardinality Indexes -- ============================================ -- Replace full index on users.active with partial index -- Most users are active, so only index the exceptions DROP INDEX IF EXISTS idx_user_active; CREATE INDEX IF NOT EXISTS idx_user_inactive ON users (id) WHERE active = false; -- ============================================ -- STEP 3: Add Composite Indexes for Common Queries -- ============================================ -- Optimize query: "find plans by profile within date range" CREATE INDEX IF NOT EXISTS idx_plan_profile_date ON plans (profile_id, date) WHERE profile_id IS NOT NULL; -- Add index for plan song lookups (if not exists) CREATE INDEX IF NOT EXISTS idx_plan_songs_song ON plan_songs (song_id); -- Add index for profile song reverse lookups (if not exists) CREATE INDEX IF NOT EXISTS idx_profile_songs_song ON profile_songs (song_id); -- ============================================ -- STEP 4: Verify Index Effectiveness -- ============================================ -- Show all indexes after cleanup SELECT schemaname, tablename, indexname, indexdef FROM pg_indexes WHERE schemaname = 'public' ORDER BY tablename, indexname; -- Show index sizes SELECT schemaname, tablename, indexname, pg_size_pretty(pg_relation_size(indexrelid)) as index_size FROM pg_stat_user_indexes WHERE schemaname = 'public' ORDER BY pg_relation_size(indexrelid) DESC; -- ============================================ -- STEP 5: Analyze Tables for Query Planner -- ============================================ -- Update statistics for query planner optimization ANALYZE users; ANALYZE profiles; ANALYZE songs; ANALYZE plans; ANALYZE plan_songs; ANALYZE profile_songs; ANALYZE profile_song_keys; ANALYZE biometric_credentials; -- ============================================ -- VERIFICATION QUERIES -- ============================================ -- Check for unused indexes (run after a week of production use) -- SELECT -- schemaname, -- tablename, -- indexname, -- idx_scan as scans, -- pg_size_pretty(pg_relation_size(indexrelid)) as size -- FROM pg_stat_user_indexes -- WHERE schemaname = 'public' -- AND idx_scan = 0 -- AND indexrelid IS NOT NULL -- ORDER BY pg_relation_size(indexrelid) DESC; -- Check for missing indexes on foreign keys -- SELECT -- c.conname AS constraint_name, -- t.relname AS table_name, -- ARRAY_AGG(a.attname ORDER BY u.attposition) AS columns -- FROM pg_constraint c -- JOIN pg_class t ON c.conrelid = t.oid -- JOIN pg_namespace n ON t.relnamespace = n.oid -- JOIN LATERAL UNNEST(c.conkey) WITH ORDINALITY AS u(attnum, attposition) ON TRUE -- JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = u.attnum -- WHERE c.contype = 'f' -- AND n.nspname = 'public' -- GROUP BY c.conname, t.relname; COMMIT;