From 59f0c208727cc5ebe65886f4e9bbdba160c256d3 Mon Sep 17 00:00:00 2001 From: Wendell Jones Date: Tue, 14 Jul 2026 11:26:18 -0400 Subject: [PATCH] Optimize series and presentation unique key grouping to preserve sort order and reduce memory usage --- .../Item/BaseItemRepository.cs | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs index 46083f7d..e173c92d 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs @@ -890,17 +890,28 @@ public sealed class BaseItemRepository /// Applies series grouping at the database level using GROUP BY to avoid loading all episodes into memory. /// This is crucial for TV Shows performance - instead of loading 10,000 episodes and deduplicating in-memory, /// we group by SeriesPresentationUniqueKey at the database level and only load one episode per series. + /// IMPORTANT: Gets sorted IDs first to preserve sort order from the ORDER BY clause. /// private async Task> ApplySeriesGroupingAtDatabaseLevel(IQueryable dbQuery, JellyfinDbContext context, CancellationToken cancellationToken) { - // Get distinct series presentation keys along with one ID per series - // This uses GROUP BY at the database level instead of in-memory DISTINCT - var groupedBySeriesIds = await dbQuery - .GroupBy(e => e.TvExtras!.SeriesPresentationUniqueKey) - .Select(g => g.First().Id) + // Get all IDs in sorted order FIRST (preserves the ORDER BY from ApplyOrder) + var sortedIds = await dbQuery + .Select(e => new { e.Id, SeriesPresentationUniqueKey = e.TvExtras!.SeriesPresentationUniqueKey }) .ToListAsync(cancellationToken) .ConfigureAwait(false); + // Deduplicate by SeriesPresentationUniqueKey while preserving the sorted order + var seenSeries = new HashSet(); + var groupedBySeriesIds = new List(); + + foreach (var item in sortedIds) + { + if (seenSeries.Add(item.SeriesPresentationUniqueKey ?? string.Empty)) + { + groupedBySeriesIds.Add(item.Id); + } + } + return groupedBySeriesIds; } @@ -908,17 +919,28 @@ public sealed class BaseItemRepository /// Applies presentation unique key grouping at the database level using GROUP BY to avoid loading duplicate items into memory. /// This optimization applies to movies, music videos, and other media types that can have duplicate PresentationUniqueKey values. /// Instead of loading all items and deduplicating in-memory, we group by PresentationUniqueKey at the database level. + /// IMPORTANT: Gets sorted IDs first to preserve sort order from the ORDER BY clause. /// private async Task> ApplyPresentationUniqueKeyGrouping(IQueryable dbQuery, JellyfinDbContext context, CancellationToken cancellationToken) { - // Get distinct presentation keys along with one ID per unique key - // This uses GROUP BY at the database level instead of in-memory DISTINCT - var groupedByPresentationKeyIds = await dbQuery - .GroupBy(e => e.PresentationUniqueKey) - .Select(g => g.First().Id) + // Get all IDs in sorted order FIRST (preserves the ORDER BY from ApplyOrder) + var sortedIds = await dbQuery + .Select(e => new { e.Id, e.PresentationUniqueKey }) .ToListAsync(cancellationToken) .ConfigureAwait(false); + // Deduplicate by PresentationUniqueKey while preserving the sorted order + var seenKeys = new HashSet(); + var groupedByPresentationKeyIds = new List(); + + foreach (var item in sortedIds) + { + if (seenKeys.Add(item.PresentationUniqueKey ?? string.Empty)) + { + groupedByPresentationKeyIds.Add(item.Id); + } + } + return groupedByPresentationKeyIds; }