-- PostgreSQL Performance Indexes for Jellyfin -- Run this script to improve query performance -- Safe to run multiple times (uses IF NOT EXISTS) \timing on \echo 'Creating performance indexes for Jellyfin PostgreSQL database...' -- ============================================================================ -- BaseItems Table Indexes -- ============================================================================ \echo 'Creating indexes on BaseItems table...' -- Index for filtering by type and virtual items (improves library filtering) CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_baseitems_type_isvirtual ON library."BaseItems" (Type, BaseItems.IsVirtualItem) WHERE IsVirtualItem = false; -- Index for parent-child relationships (improves hierarchy queries) CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_baseitems_parent_type ON library."BaseItems" (BaseItems.ParentId, Type) WHERE ParentId IS NOT NULL; -- Index for date-based queries (recently added items, sorting) CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_baseitems_datecreated ON library."BaseItems" (BaseItems.DateCreated DESC) WHERE DateCreated IS NOT NULL; -- Index for date modified (sync operations) CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_baseitems_datemodified ON library."BaseItems" (BaseItems.DateModified DESC) WHERE DateModified IS NOT NULL; -- Index for sorting by name (alphabetical browsing) CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_baseitems_sortname ON library."BaseItems" (BaseItems.SortName) WHERE SortName IS NOT NULL; -- Index for TopParent lookups (library organization) CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_baseitems_topparent_type ON library."BaseItems" (BaseItems.TopParentId, Type) WHERE TopParentId IS NOT NULL; -- Index for premiere date (upcoming/recent content) CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_baseitems_premieredate ON library."BaseItems" (BaseItems.PremiereDate DESC) WHERE PremiereDate IS NOT NULL; -- Index for production year (decade/year filtering) CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_baseitems_productionyear ON library."BaseItems" (BaseItems.ProductionYear DESC) WHERE ProductionYear IS NOT NULL; -- Index for community rating (sorting by rating) CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_baseitems_rating ON library."BaseItems" (BaseItems.CommunityRating DESC NULLS LAST); -- Index for series episodes CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_baseitems_series_episode ON library."BaseItems" (BaseItems.SeriesPresentationUniqueKey, BaseItems.IndexNumber, BaseItems.ParentIndexNumber) WHERE Type = 'MediaBrowser.Controller.Entities.TV.Episode'; -- ============================================================================ -- BaseItemProviders Table Indexes -- ============================================================================ \echo 'Creating indexes on BaseItemProviders table...' -- Index for provider value lookups (IMDb, TMDB, etc.) CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_baseitem_providers_value ON library."BaseItemProviders" (ProviderValue, ProviderId); -- Index for finding all items by provider (reverse lookup) CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_baseitem_providers_id ON library."BaseItemProviders" (ProviderId, ItemId); -- ============================================================================ -- UserData Table Indexes -- ============================================================================ \echo 'Creating indexes on UserData table...' -- Index for finding user's watched items CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_userdata_user_played ON library."UserData" (UserId, Played); -- Index for finding user's favorite items CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_userdata_user_favorite ON library."UserData" (UserId, IsFavorite) WHERE IsFavorite = true; -- Index for last played date CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_userdata_lastplayed ON library."UserData" (LastPlayedDate DESC) WHERE LastPlayedDate IS NOT NULL; -- ============================================================================ -- PeopleBaseItemMap Table Indexes -- ============================================================================ \echo 'Creating indexes on PeopleBaseItemMap table...' -- Index for finding all items for a person CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_people_items_person ON library."PeopleBaseItemMap" (PeopleId, ItemId); -- Index for finding all people for an item (already covered by FK, but explicit) CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_people_items_item ON library."PeopleBaseItemMap" (ItemId, PeopleId); -- ============================================================================ -- ItemValuesMap Table Indexes -- ============================================================================ \echo 'Creating indexes on ItemValuesMap table...' -- Index for finding items by value (genres, tags, etc.) CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_itemvalues_map_value ON library."ItemValuesMap" (ItemValueId, ItemId); -- ============================================================================ -- ActivityLog Table Indexes -- ============================================================================ \echo 'Creating indexes on ActivityLog table...' -- Index for date filtering (recent activity) CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_activitylog_date ON activitylog."ActivityLog" (DateCreated DESC); -- Index for user activity CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_activitylog_user ON activitylog."ActivityLog" (UserId, DateCreated DESC) WHERE UserId IS NOT NULL; -- ============================================================================ -- MediaStreamInfos Table Indexes -- ============================================================================ \echo 'Creating indexes on MediaStreamInfos table...' -- Index for finding streams by item and type CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_mediastreams_item_type ON library."MediaStreamInfos" (ItemId, Type); -- Index for codec searches CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_mediastreams_codec ON library."MediaStreamInfos" (Codec) WHERE Codec IS NOT NULL; -- ============================================================================ -- Analyze Tables -- ============================================================================ \echo 'Analyzing tables to update statistics...' ANALYZE library."BaseItems"; ANALYZE library."BaseItemProviders"; ANALYZE library."UserData"; ANALYZE library."PeopleBaseItemMap"; ANALYZE library."ItemValuesMap"; ANALYZE library."MediaStreamInfos"; -- Analyze ActivityLog if it exists DO $$ BEGIN IF EXISTS ( SELECT 1 FROM information_schema.tables WHERE table_schema = 'activitylog' AND table_name = 'ActivityLog' ) THEN ANALYZE activitylog."ActivityLog"; RAISE NOTICE 'ActivityLog analyzed'; END IF; END $$; -- ============================================================================ -- Report Index Usage -- ============================================================================ \echo 'Current index usage statistics:' 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 FROM pg_stat_user_indexes WHERE schemaname IN ('library', 'activitylog', 'users', 'authentication', 'displaypreferences') ORDER BY schemaname, relname, indexrelname; \echo '' \echo 'Index creation complete!' \echo '' \echo 'Note: CONCURRENTLY builds indexes without blocking writes, but takes longer.' \echo 'Monitor progress with: SELECT * FROM pg_stat_progress_create_index;' \echo '' \echo 'After indexing, restart Jellyfin to clear connection pools.'