From c091ffdc6b2d8d4dd6f439d056c561bae7bd9a18 Mon Sep 17 00:00:00 2001 From: brandon Date: Fri, 7 Aug 2026 22:51:45 -0400 Subject: Batch alternate version detection in DtoService to remove MediaSourceCount N+1 Browsing a page of videos with the MediaSourceCount field ran one alternate version query per item, each opening a fresh DbContext. On a large library that turned a single page into hundreds of sequential round trips and made the Items endpoint take tens of seconds while holding a request thread the whole time. Detect which videos own alternate versions once per page with a single query, mirroring the existing people batch. Videos absent from that set have a single media source, so the per item lookups are skipped for the common case. Behavior is unchanged: a video with no alternates already resolved to a count of one. Adds a regression test asserting the count resolves from the batch and the per item lookups are never called. --- .../Dto/DtoServiceImageInheritanceTests.cs | 42 ++++++++++++++++++++++ 1 file changed, 42 insertions(+) (limited to 'tests') diff --git a/tests/Jellyfin.Server.Implementations.Tests/Dto/DtoServiceImageInheritanceTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Dto/DtoServiceImageInheritanceTests.cs index d18f8c6cff..fa94250287 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Dto/DtoServiceImageInheritanceTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Dto/DtoServiceImageInheritanceTests.cs @@ -9,6 +9,7 @@ using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Providers; @@ -205,6 +206,43 @@ public class DtoServiceImageInheritanceTests libraryManager.Verify(x => x.GetPeople(It.IsAny()), Times.Never); } + [Fact] + public void GetBaseItemDtos_Videos_ResolveMediaSourceCountFromBatch_WithoutPerItemLookup() + { + static Movie MakeMovie() => new Movie + { + Id = Guid.NewGuid(), + Name = "Movie", + ImageInfos = [] + }; + + var movieOne = MakeMovie(); + var movieTwo = MakeMovie(); + + var libraryManager = new Mock(); + + // DtoService detects which videos own alternate versions in ONE batch + // (GetItemsWithAlternateVersions) before the per-item loop. Videos absent from that set have a + // single media source, so the per-item GetLinkedAlternateVersions/GetLocalAlternateVersionIds + // queries (the N+1) must be skipped entirely. Here neither movie has alternate versions. + libraryManager + .Setup(x => x.GetItemsWithAlternateVersions(It.IsAny>())) + .Returns(new HashSet()); + + var dtoService = BuildDtoService(libraryManager); + + var options = new DtoOptions(false) { Fields = [ItemFields.MediaSourceCount] }; + var dtos = dtoService.GetBaseItemDtos([movieOne, movieTwo], options); + + Assert.Equal(2, dtos.Count); + + // The alternate-version check is batched once for the whole set, and the per-item lookups are + // never reached because the batch already ruled out alternate versions. + libraryManager.Verify(x => x.GetItemsWithAlternateVersions(It.IsAny>()), Times.Once); + libraryManager.Verify(x => x.GetLinkedAlternateVersions(It.IsAny