-- PostgreSQL Performance Indexes for Jellyfin -- Based on actual schema analysis -- Run this script to add supplementary performance indexes -- Safe to run multiple times \pset pager off \timing on \echo '========================================'; \echo 'Jellyfin PostgreSQL Supplementary Indexes'; \echo '========================================'; \echo ''; \echo 'Your schema already has comprehensive indexes from EF Core migrations.'; \echo 'This script adds only missing supplementary indexes.'; \echo ''; -- ============================================================================ -- Check Existing Indexes -- ============================================================================ \echo 'Analyzing existing indexes...'; DO $$ DECLARE idx_count int; BEGIN SELECT count(*) INTO idx_count FROM pg_indexes WHERE schemaname = 'library' AND tablename = 'BaseItems'; RAISE NOTICE 'Found % existing indexes on BaseItems table', idx_count; END $$; -- ============================================================================ -- BaseItems - Add Only Missing Supplementary Indexes -- ============================================================================ \echo 'Adding supplementary BaseItems indexes...'; -- Composite index for filtered library browsing (type + virtual + parent) DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_indexes WHERE schemaname = 'library' AND tablename = 'BaseItems' AND indexname = 'idx_baseitems_type_isvirtualitem_topparentid' ) THEN CREATE INDEX CONCURRENTLY idx_baseitems_type_isvirtualitem_topparentid ON library."BaseItems" ("Type", "IsVirtualItem", "TopParentId") WHERE "IsVirtualItem" = false; RAISE NOTICE 'Created idx_baseitems_type_isvirtualitem_topparentid'; ELSE RAISE NOTICE 'Index idx_baseitems_type_isvirtualitem_topparentid already exists'; END IF; END $$; -- Index for folder hierarchy queries DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_indexes WHERE schemaname = 'library' AND tablename = 'BaseItems' AND indexname = 'idx_baseitems_topparentid_isfolder' ) THEN CREATE INDEX CONCURRENTLY idx_baseitems_topparentid_isfolder ON library."BaseItems" ("TopParentId", "IsFolder", "IsVirtualItem") WHERE "TopParentId" IS NOT NULL; RAISE NOTICE 'Created idx_baseitems_topparentid_isfolder'; ELSE RAISE NOTICE 'Index idx_baseitems_topparentid_isfolder already exists'; END IF; END $$; -- Index for recently added content with filters DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_indexes WHERE schemaname = 'library' AND tablename = 'BaseItems' AND indexname = 'idx_baseitems_datecreated_filtered' ) THEN CREATE INDEX CONCURRENTLY idx_baseitems_datecreated_filtered ON library."BaseItems" ("DateCreated" DESC, "Type", "IsVirtualItem") WHERE "DateCreated" IS NOT NULL AND "IsVirtualItem" = false; RAISE NOTICE 'Created idx_baseitems_datecreated_filtered'; ELSE RAISE NOTICE 'Index idx_baseitems_datecreated_filtered already exists'; END IF; END $$; -- ============================================================================ -- ItemValuesMap - Add Reverse Lookup Index -- ============================================================================ \echo 'Checking ItemValuesMap indexes...'; DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_indexes WHERE schemaname = 'library' AND tablename = 'ItemValuesMap' AND indexname = 'idx_itemvaluesmap_itemvalueid_itemid' ) THEN CREATE INDEX CONCURRENTLY idx_itemvaluesmap_itemvalueid_itemid ON library."ItemValuesMap" ("ItemValueId", "ItemId"); RAISE NOTICE 'Created idx_itemvaluesmap_itemvalueid_itemid'; ELSE RAISE NOTICE 'Index idx_itemvaluesmap_itemvalueid_itemid already exists'; END IF; END $$; -- ============================================================================ -- ActivityLogs - Add User-Specific Index -- (Note: Table is ActivityLogs, not ActivityLog) -- ============================================================================ \echo 'Checking ActivityLogs indexes...'; DO $$ BEGIN IF EXISTS ( SELECT 1 FROM information_schema.tables WHERE table_schema = 'activitylog' AND table_name = 'ActivityLogs' ) THEN IF NOT EXISTS ( SELECT 1 FROM pg_indexes WHERE schemaname = 'activitylog' AND tablename = 'ActivityLogs' AND indexname = 'idx_activitylogs_userid_datecreated' ) THEN CREATE INDEX CONCURRENTLY idx_activitylogs_userid_datecreated ON activitylog."ActivityLogs" ("UserId", "DateCreated" DESC) WHERE "UserId" IS NOT NULL; RAISE NOTICE 'Created idx_activitylogs_userid_datecreated'; ELSE RAISE NOTICE 'Index idx_activitylogs_userid_datecreated already exists'; END IF; ELSE RAISE NOTICE 'ActivityLogs table not found (expected: activitylog.ActivityLogs)'; END IF; END $$; -- ============================================================================ -- Analyze Tables to Update Statistics -- ============================================================================ \echo ''; \echo 'Updating table statistics...'; ANALYZE library."BaseItems"; ANALYZE library."BaseItemProviders"; ANALYZE library."UserData"; ANALYZE library."PeopleBaseItemMap"; ANALYZE library."ItemValuesMap"; ANALYZE library."ItemValues"; ANALYZE library."MediaStreamInfos"; ANALYZE library."Peoples"; ANALYZE library."Chapters"; ANALYZE library."KeyframeData"; DO $$ BEGIN IF EXISTS ( SELECT 1 FROM information_schema.tables WHERE table_schema = 'activitylog' AND table_name = 'ActivityLogs' ) THEN ANALYZE activitylog."ActivityLogs"; RAISE NOTICE 'ActivityLogs statistics updated'; END IF; END $$; \echo 'Statistics updated.'; -- ============================================================================ -- Report Current Index Status -- ============================================================================ \echo ''; \echo '========================================'; \echo 'Index Usage Report'; \echo '========================================'; -- Show all indexes and their usage SELECT schemaname, relname as tablename, indexrelname as indexname, pg_size_pretty(pg_relation_size(indexrelid)) as index_size, idx_scan as times_used, idx_tup_read as rows_read, idx_tup_fetch as rows_fetched, CASE WHEN idx_scan = 0 THEN 'UNUSED' WHEN idx_scan < 10 THEN 'RARELY USED' WHEN idx_scan < 100 THEN 'OCCASIONALLY USED' ELSE 'FREQUENTLY USED' END as usage_category FROM pg_stat_user_indexes WHERE schemaname IN ('library', 'activitylog', 'users', 'authentication', 'displaypreferences') ORDER BY schemaname, relname, idx_scan DESC; \echo ''; \echo '========================================'; \echo 'Summary'; \echo '========================================'; \echo 'Your database already had comprehensive indexes from EF Core migrations.'; \echo 'Added supplementary composite indexes for specific query patterns.'; \echo ''; \echo 'Next steps:'; \echo '1. Monitor performance with: psql -U jellyfin -d jellyfin -f sql/diagnostics.sql'; \echo '2. Check query plans for slow queries'; \echo '3. Consider removing rarely-used indexes after 30 days'; \echo '4. Restart Jellyfin to clear connection pools'; \echo ''; \echo 'See SCHEMA_ANALYSIS.md for detailed index information.'; \echo '========================================';