From dd5c932bf6c24d7d554615d0ce49b35181bb47ec Mon Sep 17 00:00:00 2001 From: Wendell Jones Date: Thu, 9 Jul 2026 15:30:57 -0400 Subject: [PATCH] Enhance PostgreSQL diagnostics script with improved connection pool status and query formatting --- scripts/sql/diagnostics/diagnostics.sql | 114 +++++++++++++----------- 1 file changed, 60 insertions(+), 54 deletions(-) diff --git a/scripts/sql/diagnostics/diagnostics.sql b/scripts/sql/diagnostics/diagnostics.sql index 9a868b40..54cb3479 100644 --- a/scripts/sql/diagnostics/diagnostics.sql +++ b/scripts/sql/diagnostics/diagnostics.sql @@ -1,5 +1,6 @@ -- PostgreSQL Performance Diagnostics for Jellyfin -- Run this to diagnose current performance issues +\pset pager off \timing on \x auto @@ -16,14 +17,14 @@ \echo '1. CONNECTION POOL STATUS' \echo '-----------------------------------------' -SELECT +SELECT count(*) as total_connections, count(*) FILTER (WHERE state = 'active') as active, count(*) FILTER (WHERE state = 'idle') as idle, count(*) FILTER (WHERE state = 'idle in transaction') as idle_in_transaction, count(*) FILTER (WHERE state = 'idle in transaction (aborted)') as aborted, count(*) FILTER (WHERE wait_event_type IS NOT NULL) as waiting -FROM pg_stat_activity +FROM pg_stat_activity WHERE datname = 'jellyfin'; \echo '' @@ -35,7 +36,7 @@ WHERE datname = 'jellyfin'; \echo '2. LONG-RUNNING QUERIES (>1 second)' \echo '-----------------------------------------' -SELECT +SELECT pid, usename, application_name, @@ -45,8 +46,8 @@ SELECT wait_event, left(query, 100) as query_preview FROM pg_stat_activity -WHERE datname = 'jellyfin' - AND state != 'idle' +WHERE datname = 'jellyfin' + AND state != 'idle' AND query_start < now() - interval '1 second' ORDER BY duration DESC LIMIT 10; @@ -60,16 +61,21 @@ LIMIT 10; \echo '3. TABLE SIZES (Top 10)' \echo '-----------------------------------------' -SELECT +SELECT s.schemaname, - s.relname as tablename, - pg_size_pretty(pg_total_relation_size(s.schemaname||'.'||s.relname)) AS total_size, - pg_size_pretty(pg_relation_size(s.schemaname||'.'||s.relname)) AS table_size, - pg_size_pretty(pg_total_relation_size(s.schemaname||'.'||s.relname) - pg_relation_size(s.schemaname||'.'||s.relname)) AS index_size, - s.n_live_tup as row_count -FROM pg_stat_user_tables s + s.relname AS tablename, + pg_size_pretty(pg_total_relation_size(fqname)) AS total_size, + pg_size_pretty(pg_relation_size(fqname)) AS table_size, + pg_size_pretty(pg_total_relation_size(fqname) - pg_relation_size(fqname)) AS index_size, + s.n_live_tup AS row_count +FROM ( + SELECT + s.*, + quote_ident(s.schemaname) || '.' || quote_ident(s.relname) AS fqname + FROM pg_stat_user_tables s +) s WHERE s.schemaname IN ('library', 'users', 'authentication', 'displaypreferences', 'activitylog') -ORDER BY pg_total_relation_size(s.schemaname||'.'||s.relname) DESC +ORDER BY pg_total_relation_size(fqname) DESC LIMIT 10; \echo '' @@ -81,25 +87,25 @@ LIMIT 10; \echo '4. INDEX USAGE (Tables with low index usage)' \echo '-----------------------------------------' -SELECT +SELECT schemaname, relname as tablename, seq_scan, idx_scan, n_live_tup as rows, - CASE - WHEN seq_scan + idx_scan > 0 + CASE + WHEN seq_scan + idx_scan > 0 THEN round(idx_scan::numeric / (seq_scan + idx_scan) * 100, 2) - ELSE 0 + ELSE 0 END as index_usage_percent FROM pg_stat_user_tables WHERE schemaname IN ('library', 'users', 'authentication', 'displaypreferences', 'activitylog') AND n_live_tup > 100 -ORDER BY - CASE - WHEN seq_scan + idx_scan > 0 +ORDER BY + CASE + WHEN seq_scan + idx_scan > 0 THEN idx_scan::numeric / (seq_scan + idx_scan) - ELSE 0 + ELSE 0 END ASC LIMIT 10; @@ -112,17 +118,17 @@ LIMIT 10; \echo '5. TABLES WITH HIGH SEQUENTIAL SCANS' \echo '-----------------------------------------' -SELECT +SELECT schemaname, relname as tablename, seq_scan, seq_tup_read, idx_scan, n_live_tup, - CASE - WHEN seq_scan > 0 - THEN seq_tup_read / seq_scan - ELSE 0 + CASE + WHEN seq_scan > 0 + THEN seq_tup_read / seq_scan + ELSE 0 END as avg_rows_per_seq_scan FROM pg_stat_user_tables WHERE schemaname IN ('library', 'users', 'authentication', 'displaypreferences', 'activitylog') @@ -140,15 +146,15 @@ LIMIT 10; \echo '6. TABLE BLOAT (Dead Tuples)' \echo '-----------------------------------------' -SELECT +SELECT schemaname, relname as tablename, n_dead_tup as dead_tuples, n_live_tup as live_tuples, - CASE - WHEN n_live_tup > 0 + CASE + WHEN n_live_tup > 0 THEN round(n_dead_tup::numeric / n_live_tup * 100, 2) - ELSE 0 + ELSE 0 END as dead_tuple_percent, last_vacuum, last_autovacuum, @@ -169,7 +175,7 @@ LIMIT 10; \echo '7. CACHE HIT RATIO (Should be >95%)' \echo '-----------------------------------------' -SELECT +SELECT 'Buffer Cache Hit Ratio' as metric, round( sum(blks_hit) / nullif(sum(blks_hit) + sum(blks_read), 0) * 100, @@ -187,7 +193,7 @@ WHERE datname = 'jellyfin'; \echo '8. UNUSED OR RARELY USED INDEXES' \echo '-----------------------------------------' -SELECT +SELECT schemaname, relname as tablename, indexrelname as indexname, @@ -199,8 +205,8 @@ FROM pg_stat_user_indexes WHERE schemaname IN ('library', 'users', 'authentication', 'displaypreferences', 'activitylog') AND idx_scan < 50 AND pg_relation_size(indexrelid) > 1000000 -ORDER BY pg_relation_size(indexrelid) DESC -LIMIT 10; +ORDER BY tablename, pg_relation_size(indexrelid) DESC; +--LIMIT 10; \echo '' @@ -211,7 +217,7 @@ LIMIT 10; \echo '9. CURRENT LOCKS (Blocked queries)' \echo '-----------------------------------------' -SELECT +SELECT bl.pid AS blocked_pid, a.usename AS blocked_user, ka.query AS blocking_query, @@ -250,8 +256,8 @@ DECLARE query_rec RECORD; BEGIN IF EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pg_stat_statements') THEN - FOR query_rec IN - SELECT + FOR query_rec IN + SELECT calls, round(mean_exec_time::numeric, 2) as avg_time_ms, round(max_exec_time::numeric, 2) as max_time_ms, @@ -264,12 +270,12 @@ BEGIN ORDER BY mean_exec_time DESC LIMIT 10 LOOP - RAISE NOTICE 'Calls: %, Avg: %ms, Max: %ms, Total: %ms, %%: %, Query: %', - query_rec.calls, - query_rec.avg_time_ms, - query_rec.max_time_ms, - query_rec.total_time_ms, - query_rec.percent_total, + RAISE NOTICE 'Calls: %, Avg: %ms, Max: %ms, Total: %ms, %%: %, Query: %', + query_rec.calls, + query_rec.avg_time_ms, + query_rec.max_time_ms, + query_rec.total_time_ms, + query_rec.percent_total, query_rec.query_preview; END LOOP; END IF; @@ -284,7 +290,7 @@ END $$; \echo '11. KEY POSTGRESQL SETTINGS' \echo '-----------------------------------------' -SELECT +SELECT name, setting, unit, @@ -324,42 +330,42 @@ BEGIN -- Check connection count SELECT count(*) INTO conn_count FROM pg_stat_activity WHERE datname = 'jellyfin'; - + SELECT setting::int INTO max_conn FROM pg_settings WHERE name = 'max_connections'; - + IF conn_count::numeric / max_conn > 0.8 THEN RAISE WARNING 'High connection usage: % of % max connections', conn_count, max_conn; RAISE NOTICE 'RECOMMENDATION: Increase max_connections in postgresql.conf'; END IF; - + -- Check cache hit ratio SELECT round( sum(blks_hit) / nullif(sum(blks_hit) + sum(blks_read), 0) * 100, 2 ) INTO hit_ratio FROM pg_stat_database WHERE datname = 'jellyfin'; - + IF hit_ratio < 95 THEN - RAISE WARNING 'Low cache hit ratio: %%. Target: >95%%', hit_ratio; + RAISE WARNING 'Low cache hit ratio: %. Target: >95%%', hit_ratio; RAISE NOTICE 'RECOMMENDATION: Increase shared_buffers and effective_cache_size'; END IF; - + -- Check bloat SELECT count(*) INTO bloat_tables FROM pg_stat_user_tables WHERE n_dead_tup > 1000 AND schemaname IN ('library', 'users', 'authentication'); - + IF bloat_tables > 0 THEN RAISE WARNING 'Found % tables with significant dead tuples', bloat_tables; RAISE NOTICE 'RECOMMENDATION: Run VACUUM ANALYZE or adjust autovacuum settings'; END IF; - - RAISE NOTICE 'Diagnostic report complete.'; + + -- RAISE NOTICE 'Diagnostic report complete.'; END $$; \echo '' \echo '=========================================' \echo 'Run sql/performance_indexes.sql to add missing indexes' \echo 'See PERFORMANCE_TROUBLESHOOTING.md for detailed fixes' -\echo '=========================================' +\echo '=========================================' \ No newline at end of file