1-- Database Performance Dashboard 2-- Shows aggregated database query metrics grouped by query pattern 3 4WITH query_classification AS ( 5 -- Classify queries by type and extract table names from actual SQL 6 SELECT 7 span_name, 8 attributes->>'db.statement' as query_text, 9 duration, 10 start_timestamp, 11 trace_id, 12 otel_status_code, 13 CASE 14 WHEN span_name LIKE 'SELECT%' THEN 'SELECT' 15 WHEN span_name LIKE 'INSERT%' THEN 'INSERT' 16 WHEN span_name LIKE 'UPDATE%' THEN 'UPDATE' 17 WHEN span_name LIKE 'DELETE%' THEN 'DELETE' 18 WHEN span_name LIKE 'BEGIN%' THEN 'TRANSACTION' 19 WHEN span_name LIKE 'COMMIT%' THEN 'TRANSACTION' 20 WHEN span_name LIKE 'ROLLBACK%' THEN 'TRANSACTION' 21 ELSE 'OTHER' 22 END AS query_type, 23 -- Extract primary table from actual SQL query in attributes 24 CASE 25 -- tracks queries (most common) 26 WHEN attributes->>'db.statement' LIKE '%FROM tracks%' AND attributes->>'db.statement' NOT LIKE '%JOIN%' THEN 'tracks' 27 WHEN attributes->>'db.statement' LIKE '%FROM tracks JOIN%' THEN 'tracks+joins' 28 WHEN attributes->>'db.statement' LIKE '%INTO tracks%' THEN 'tracks' 29 WHEN attributes->>'db.statement' LIKE '%UPDATE tracks%' THEN 'tracks' 30 31 -- track_likes queries 32 WHEN attributes->>'db.statement' LIKE '%FROM track_likes%' THEN 'track_likes' 33 WHEN attributes->>'db.statement' LIKE '%INTO track_likes%' THEN 'track_likes' 34 35 -- user auth/session queries 36 WHEN attributes->>'db.statement' LIKE '%FROM user_sessions%' THEN 'user_sessions' 37 WHEN attributes->>'db.statement' LIKE '%INTO user_sessions%' THEN 'user_sessions' 38 WHEN attributes->>'db.statement' LIKE '%UPDATE user_sessions%' THEN 'user_sessions' 39 40 -- user preferences 41 WHEN attributes->>'db.statement' LIKE '%FROM user_preferences%' THEN 'user_preferences' 42 WHEN attributes->>'db.statement' LIKE '%INTO user_preferences%' THEN 'user_preferences' 43 WHEN attributes->>'db.statement' LIKE '%UPDATE user_preferences%' THEN 'user_preferences' 44 45 -- queue state 46 WHEN attributes->>'db.statement' LIKE '%FROM queue_state%' THEN 'queue_state' 47 WHEN attributes->>'db.statement' LIKE '%INTO queue_state%' THEN 'queue_state' 48 WHEN attributes->>'db.statement' LIKE '%UPDATE queue_state%' THEN 'queue_state' 49 50 -- artists 51 WHEN attributes->>'db.statement' LIKE '%FROM artists%' THEN 'artists' 52 WHEN attributes->>'db.statement' LIKE '%INTO artists%' THEN 'artists' 53 WHEN attributes->>'db.statement' LIKE '%UPDATE artists%' THEN 'artists' 54 55 -- albums 56 WHEN attributes->>'db.statement' LIKE '%FROM albums%' THEN 'albums' 57 WHEN attributes->>'db.statement' LIKE '%INTO albums%' THEN 'albums' 58 59 ELSE 'other/unknown' 60 END AS primary_table 61 FROM records 62 WHERE 63 -- Filter for database operations 64 (span_name = 'SELECT neondb' 65 OR span_name = 'INSERT neondb' 66 OR span_name = 'UPDATE neondb' 67 OR span_name = 'DELETE neondb' 68 OR span_name = 'BEGIN neondb' 69 OR span_name = 'COMMIT neondb' 70 OR span_name = 'ROLLBACK neondb') 71 -- Exclude very fast queries (connection pool pings, etc.) 72 AND duration > 0.001 73), 74aggregated_metrics AS ( 75 -- Aggregate by query type and table 76 SELECT 77 query_type, 78 primary_table, 79 COUNT(*) AS total_queries, 80 ROUND(AVG(duration * 1000)::numeric, 2) AS avg_duration_ms, 81 ROUND(PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY duration * 1000)::numeric, 2) AS p50_duration_ms, 82 ROUND(PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY duration * 1000)::numeric, 2) AS p95_duration_ms, 83 ROUND(PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY duration * 1000)::numeric, 2) AS p99_duration_ms, 84 ROUND(MAX(duration * 1000)::numeric, 2) AS max_duration_ms, 85 COUNT(*) FILTER (WHERE otel_status_code = 'ERROR') AS error_count, 86 ROUND((COUNT(*) FILTER (WHERE otel_status_code = 'ERROR')::numeric / COUNT(*)::numeric * 100), 2) AS error_rate_pct 87 FROM query_classification 88 GROUP BY query_type, primary_table 89) 90SELECT 91 query_type AS "Query Type", 92 primary_table AS "Table", 93 total_queries AS "Count", 94 avg_duration_ms AS "Avg (ms)", 95 p50_duration_ms AS "p50 (ms)", 96 p95_duration_ms AS "p95 (ms)", 97 p99_duration_ms AS "p99 (ms)", 98 max_duration_ms AS "Max (ms)", 99 error_count AS "Errors", 100 COALESCE(error_rate_pct, 0) AS "Error Rate %" 101FROM aggregated_metrics 102WHERE total_queries > 0 103ORDER BY 104 -- Sort by most impactful queries first 105 (total_queries * avg_duration_ms) DESC, 106 error_count DESC 107LIMIT 50; 108 109 110-- Alternative: Top Slowest Individual Queries 111-- Uncomment to see the actual slowest query instances instead of aggregated patterns 112/* 113SELECT 114 ROUND(duration * 1000, 2) AS "Duration (ms)", 115 CASE 116 WHEN span_name = 'SELECT neondb' THEN 'SELECT' 117 WHEN span_name = 'INSERT neondb' THEN 'INSERT' 118 WHEN span_name = 'UPDATE neondb' THEN 'UPDATE' 119 WHEN span_name = 'DELETE neondb' THEN 'DELETE' 120 ELSE 'OTHER' 121 END AS "Type", 122 LEFT(attributes->>'db.statement', 150) AS "Query Preview", 123 start_timestamp AS "Timestamp", 124 trace_id AS "Trace ID", 125 otel_status_code AS "Status" 126FROM records 127WHERE 128 (span_name = 'SELECT neondb' 129 OR span_name = 'INSERT neondb' 130 OR span_name = 'UPDATE neondb' 131 OR span_name = 'DELETE neondb') 132 AND duration > 0.001 133ORDER BY duration DESC 134LIMIT 25; 135*/ 136 137 138-- Alternative: Database Operations Timeline (5-minute aggregation) 139-- Uncomment to see query volume and performance over time 140/* 141WITH time_metrics AS ( 142 SELECT 143 DATE_TRUNC('minute', start_timestamp) AS minute, 144 CASE 145 WHEN span_name = 'SELECT neondb' THEN 'SELECT' 146 WHEN span_name = 'INSERT neondb' THEN 'INSERT' 147 WHEN span_name = 'UPDATE neondb' THEN 'UPDATE' 148 WHEN span_name = 'DELETE neondb' THEN 'DELETE' 149 ELSE 'OTHER' 150 END AS query_type, 151 COUNT(*) AS query_count, 152 ROUND(AVG(duration * 1000)::numeric, 2) AS avg_duration_ms, 153 COUNT(*) FILTER (WHERE otel_status_code = 'ERROR') AS error_count 154 FROM records 155 WHERE 156 (span_name = 'SELECT neondb' 157 OR span_name = 'INSERT neondb' 158 OR span_name = 'UPDATE neondb' 159 OR span_name = 'DELETE neondb') 160 AND start_timestamp > NOW() - INTERVAL '2 hours' 161 GROUP BY minute, query_type 162) 163SELECT 164 minute AS "Time", 165 query_type AS "Type", 166 query_count AS "Count", 167 avg_duration_ms AS "Avg Duration (ms)", 168 error_count AS "Errors" 169FROM time_metrics 170ORDER BY minute DESC, query_count DESC; 171*/ 172 173 174-- Alternative: Connection Pool and Transaction Metrics 175-- Uncomment to analyze transaction patterns and connection behavior 176/* 177WITH transaction_metrics AS ( 178 SELECT 179 DATE_TRUNC('minute', start_timestamp) AS minute, 180 COUNT(*) FILTER (WHERE span_name = 'BEGIN neondb') AS begin_count, 181 COUNT(*) FILTER (WHERE span_name = 'COMMIT neondb') AS commit_count, 182 COUNT(*) FILTER (WHERE span_name = 'ROLLBACK neondb') AS rollback_count, 183 ROUND(AVG(duration * 1000) FILTER (WHERE span_name = 'BEGIN neondb')::numeric, 2) AS avg_begin_ms, 184 ROUND(AVG(duration * 1000) FILTER (WHERE span_name = 'COMMIT neondb')::numeric, 2) AS avg_commit_ms 185 FROM records 186 WHERE 187 span_name = 'BEGIN neondb' 188 OR span_name = 'COMMIT neondb' 189 OR span_name = 'ROLLBACK neondb' 190 GROUP BY minute 191) 192SELECT 193 minute AS "Minute", 194 begin_count AS "Begins", 195 commit_count AS "Commits", 196 rollback_count AS "Rollbacks", 197 avg_begin_ms AS "Avg BEGIN (ms)", 198 avg_commit_ms AS "Avg COMMIT (ms)", 199 CASE 200 WHEN begin_count > 0 201 THEN ROUND((rollback_count::numeric / begin_count::numeric * 100), 2) 202 ELSE 0 203 END AS "Rollback Rate %" 204FROM transaction_metrics 205WHERE begin_count > 0 OR commit_count > 0 OR rollback_count > 0 206ORDER BY minute DESC 207LIMIT 60; 208*/