From 88cad2ad1acc158c48de950e0b0adb03d2d84b89 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Mon, 4 May 2026 01:56:21 +0200 Subject: Speed-up LatestItems for Music --- Emby.Server.Implementations/Dto/DtoService.cs | 50 +++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 6 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index cc57d183b6..9f36577410 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -203,6 +203,39 @@ namespace Emby.Server.Implementations.Dto } } + // Batch-fetch MusicArtist lookups across all items to avoid N+1 queries. + IReadOnlyDictionary? artistsBatch = null; + HashSet? artistNames = null; + foreach (var item in accessibleItems) + { + if (item is IHasArtist hasArtist) + { + foreach (var name in hasArtist.Artists) + { + if (!string.IsNullOrWhiteSpace(name)) + { + (artistNames ??= new HashSet(StringComparer.Ordinal)).Add(name); + } + } + } + + if (item is IHasAlbumArtist hasAlbumArtist) + { + foreach (var name in hasAlbumArtist.AlbumArtists) + { + if (!string.IsNullOrWhiteSpace(name)) + { + (artistNames ??= new HashSet(StringComparer.Ordinal)).Add(name); + } + } + } + } + + if (artistNames is { Count: > 0 }) + { + artistsBatch = _libraryManager.GetArtists(artistNames.ToArray()); + } + for (int index = 0; index < accessibleItems.Count; index++) { var item = accessibleItems[index]; @@ -214,7 +247,8 @@ namespace Emby.Server.Implementations.Dto userDataBatch?.GetValueOrDefault(item.Id), allCollectionFolders, childCountBatch, - playedCountBatch); + playedCountBatch, + artistsBatch); if (item is LiveTvChannel tvChannel) { @@ -274,7 +308,8 @@ namespace Emby.Server.Implementations.Dto UserItemData? userData = null, List? allCollectionFolders = null, Dictionary? childCountBatch = null, - Dictionary? playedCountBatch = null) + Dictionary? playedCountBatch = null, + IReadOnlyDictionary? artistsBatch = null) { var dto = new BaseItemDto { @@ -334,7 +369,7 @@ namespace Emby.Server.Implementations.Dto AttachStudios(dto, item); } - AttachBasicFields(dto, item, owner, options); + AttachBasicFields(dto, item, owner, options, artistsBatch); if (options.ContainsField(ItemFields.CanDelete)) { @@ -907,7 +942,8 @@ namespace Emby.Server.Implementations.Dto /// The item. /// The owner. /// The options. - private void AttachBasicFields(BaseItemDto dto, BaseItem item, BaseItem? owner, DtoOptions options) + /// Optional pre-fetched artist lookup shared across a batch of items. + private void AttachBasicFields(BaseItemDto dto, BaseItem item, BaseItem? owner, DtoOptions options, IReadOnlyDictionary? artistsBatch = null) { if (options.ContainsField(ItemFields.DateCreated)) { @@ -1152,7 +1188,8 @@ namespace Emby.Server.Implementations.Dto // Include artists that are not in the database yet, e.g., just added via metadata editor // var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList(); - var artistsLookup = _libraryManager.GetArtists([.. hasArtist.Artists.Where(e => !string.IsNullOrWhiteSpace(e))]); + var artistsLookup = artistsBatch + ?? _libraryManager.GetArtists([.. hasArtist.Artists.Where(e => !string.IsNullOrWhiteSpace(e))]); dto.ArtistItems = hasArtist.Artists .Where(name => !string.IsNullOrWhiteSpace(name)) @@ -1186,7 +1223,8 @@ namespace Emby.Server.Implementations.Dto // }) // .ToList(); - var albumArtistsLookup = _libraryManager.GetArtists([.. hasAlbumArtist.AlbumArtists.Where(e => !string.IsNullOrWhiteSpace(e))]); + var albumArtistsLookup = artistsBatch + ?? _libraryManager.GetArtists([.. hasAlbumArtist.AlbumArtists.Where(e => !string.IsNullOrWhiteSpace(e))]); dto.AlbumArtists = hasAlbumArtist.AlbumArtists .Where(name => !string.IsNullOrWhiteSpace(name)) -- cgit v1.2.3 From 0d58c773f9ffa33794bd272d1b0783603e2e46d6 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Mon, 4 May 2026 21:42:31 +0200 Subject: Fix review comments --- Emby.Server.Implementations/Dto/DtoService.cs | 8 ++++---- .../Item/BaseItemRepository.Querying.cs | 4 +--- Jellyfin.Server.Implementations/Item/BaseItemRepository.cs | 2 ++ 3 files changed, 7 insertions(+), 7 deletions(-) (limited to 'Emby.Server.Implementations') diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 9f36577410..94e2468719 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -205,7 +205,7 @@ namespace Emby.Server.Implementations.Dto // Batch-fetch MusicArtist lookups across all items to avoid N+1 queries. IReadOnlyDictionary? artistsBatch = null; - HashSet? artistNames = null; + var artistNames = new HashSet(StringComparer.Ordinal); foreach (var item in accessibleItems) { if (item is IHasArtist hasArtist) @@ -214,7 +214,7 @@ namespace Emby.Server.Implementations.Dto { if (!string.IsNullOrWhiteSpace(name)) { - (artistNames ??= new HashSet(StringComparer.Ordinal)).Add(name); + artistNames.Add(name); } } } @@ -225,13 +225,13 @@ namespace Emby.Server.Implementations.Dto { if (!string.IsNullOrWhiteSpace(name)) { - (artistNames ??= new HashSet(StringComparer.Ordinal)).Add(name); + artistNames.Add(name); } } } } - if (artistNames is { Count: > 0 }) + if (artistNames.Count > 0) { artistsBatch = _libraryManager.GetArtists(artistNames.ToArray()); } diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.Querying.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.Querying.cs index b7b40e76d8..85f49eeadd 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.Querying.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.Querying.cs @@ -145,13 +145,11 @@ public sealed partial class BaseItemRepository } // Albums whose Id is the parent of any track matching the user's filter. - var musicAlbumType = _itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicAlbum]!; - var albumIdsWithMatchingTrack = context.AncestorIds .Join(baseQuery, ai => ai.ItemId, t => t.Id, (ai, _) => ai.ParentItemId); var topAlbumsQuery = context.BaseItems.AsNoTracking() - .Where(album => album.Type == musicAlbumType) + .Where(album => album.Type == _musicAlbumTypeName) .Where(album => albumIdsWithMatchingTrack.Contains(album.Id)) .OrderByDescending(album => album.DateCreated) .ThenByDescending(album => album.Id); diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs index 94dedaeba8..e2c77437a8 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs @@ -39,6 +39,7 @@ public sealed partial class BaseItemRepository private readonly IItemTypeLookup _itemTypeLookup; private readonly IServerConfigurationManager _serverConfigurationManager; private readonly ILogger _logger; + private readonly string _musicAlbumTypeName; private static readonly IReadOnlyList _getAllArtistsValueTypes = [ItemValueType.Artist, ItemValueType.AlbumArtist]; private static readonly IReadOnlyList _getArtistValueTypes = [ItemValueType.Artist]; @@ -66,6 +67,7 @@ public sealed partial class BaseItemRepository _itemTypeLookup = itemTypeLookup; _serverConfigurationManager = serverConfigurationManager; _logger = logger; + _musicAlbumTypeName = itemTypeLookup.BaseItemKindNames[BaseItemKind.MusicAlbum]!; } /// -- cgit v1.2.3