-- ================================================ -- Jellyfin PostgreSQL Migration -- Migration: 20260227000000_AddSupplementaryIndexes -- Purpose: Add performance indexes for improved query performance -- ================================================ -- -- INSTRUCTIONS: -- 1. Connect to your Jellyfin PostgreSQL database -- 2. Run this script using psql, pgAdmin, or any PostgreSQL client -- 3. Verify indexes are created (see verification query at end) -- -- ================================================ BEGIN; -- Check if migration was already applied DO $$ BEGIN IF EXISTS ( SELECT 1 FROM library."__EFMigrationsHistory" WHERE "MigrationId" = '20260227000000_AddSupplementaryIndexes' ) THEN RAISE NOTICE 'Migration 20260227000000_AddSupplementaryIndexes already applied. Skipping...'; ELSE RAISE NOTICE 'Applying migration 20260227000000_AddSupplementaryIndexes...'; END IF; END $$; COMMIT; -- ================================================ -- Create Indexes (CONCURRENTLY - safe for production) -- ================================================ -- 1. Composite index for filtered library browsing (type + virtual + parent) -- Use case: Browse items by type in a specific library, excluding virtual items -- Improves queries like: "SELECT * FROM BaseItems WHERE Type='Movie' AND IsVirtualItem=false AND TopParentId='xyz'" CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_baseitems_type_isvirtualitem_topparentid ON library."BaseItems" ("Type", "IsVirtualItem", "TopParentId") WHERE "IsVirtualItem" = false; -- 2. Index for folder hierarchy queries -- Use case: Navigate folder structures efficiently -- Improves queries like: "SELECT * FROM BaseItems WHERE TopParentId='xyz' AND IsFolder=true" CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_baseitems_topparentid_isfolder ON library."BaseItems" ("TopParentId", "IsFolder", "IsVirtualItem") WHERE "TopParentId" IS NOT NULL; -- 3. Index for recently added content with filters -- Use case: "Recently Added" view across all libraries -- Improves queries like: "SELECT * FROM BaseItems WHERE IsVirtualItem=false ORDER BY DateCreated DESC" CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_baseitems_datecreated_filtered ON library."BaseItems" ("DateCreated" DESC, "Type", "IsVirtualItem") WHERE "DateCreated" IS NOT NULL AND "IsVirtualItem" = false; -- 4. Index for reverse lookup (find items by genre/tag) -- Use case: "Show all items with genre 'Action'" -- Improves queries like: "SELECT ItemId FROM ItemValuesMap WHERE ItemValueId='action-genre-id'" CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_itemvaluesmap_itemvalueid_itemid ON library."ItemValuesMap" ("ItemValueId", "ItemId"); -- 5. Index for user-specific activity queries -- Use case: "Show activity for user X" -- Improves queries like: "SELECT * FROM ActivityLogs WHERE UserId='user-id' ORDER BY DateCreated DESC" -- Note: This will fail gracefully if the activitylog.ActivityLogs table doesn't exist CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_activitylogs_userid_datecreated ON activitylog."ActivityLogs" ("UserId", "DateCreated" DESC) WHERE "UserId" IS NOT NULL; -- 6. Index for episode deduplication by PresentationUniqueKey -- Use case: Group and deduplicate episodes (used in GetItems queries) -- Improves queries like: "SELECT * FROM BaseItems WHERE Type='Episode' AND PresentationUniqueKey='xyz'" CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_baseitems_presentationuniquekey_episodes ON library."BaseItems" ("PresentationUniqueKey", "TopParentId", "IsVirtualItem") WHERE "Type" = 'MediaBrowser.Controller.Entities.TV.Episode' AND "PresentationUniqueKey" IS NOT NULL; -- ================================================ -- Update migration history -- ================================================ BEGIN; INSERT INTO library."__EFMigrationsHistory" ("MigrationId", "ProductVersion") VALUES ('20260227000000_AddSupplementaryIndexes', '11.0.0') ON CONFLICT ("MigrationId") DO NOTHING; COMMIT; -- ================================================ -- Verification Query -- ================================================ -- Run this to verify all indexes were created successfully: SELECT schemaname AS "Schema", tablename AS "Table", indexname AS "Index Name", indexdef AS "Definition" FROM pg_indexes WHERE indexname IN ( 'idx_baseitems_type_isvirtualitem_topparentid', 'idx_baseitems_topparentid_isfolder', 'idx_baseitems_datecreated_filtered', 'idx_itemvaluesmap_itemvalueid_itemid', 'idx_activitylogs_userid_datecreated', 'idx_baseitems_presentationuniquekey_episodes' ) ORDER BY schemaname, tablename, indexname; -- Expected: 6 rows (or 5 if ActivityLogs table doesn't exist)