Optimize series and presentation unique key grouping to preserve sort order and reduce memory usage

This commit is contained in:
2026-07-14 11:26:18 -04:00
parent 8914f4dce9
commit 59f0c20872
@@ -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. /// 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, /// 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. /// 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.
/// </summary> /// </summary>
private async Task<List<Guid>> ApplySeriesGroupingAtDatabaseLevel(IQueryable<BaseItemEntity> dbQuery, JellyfinDbContext context, CancellationToken cancellationToken) private async Task<List<Guid>> ApplySeriesGroupingAtDatabaseLevel(IQueryable<BaseItemEntity> dbQuery, JellyfinDbContext context, CancellationToken cancellationToken)
{ {
// Get distinct series presentation keys along with one ID per series // Get all IDs in sorted order FIRST (preserves the ORDER BY from ApplyOrder)
// This uses GROUP BY at the database level instead of in-memory DISTINCT var sortedIds = await dbQuery
var groupedBySeriesIds = await dbQuery .Select(e => new { e.Id, SeriesPresentationUniqueKey = e.TvExtras!.SeriesPresentationUniqueKey })
.GroupBy(e => e.TvExtras!.SeriesPresentationUniqueKey)
.Select(g => g.First().Id)
.ToListAsync(cancellationToken) .ToListAsync(cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
// Deduplicate by SeriesPresentationUniqueKey while preserving the sorted order
var seenSeries = new HashSet<string>();
var groupedBySeriesIds = new List<Guid>();
foreach (var item in sortedIds)
{
if (seenSeries.Add(item.SeriesPresentationUniqueKey ?? string.Empty))
{
groupedBySeriesIds.Add(item.Id);
}
}
return groupedBySeriesIds; 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. /// 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. /// 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. /// 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.
/// </summary> /// </summary>
private async Task<List<Guid>> ApplyPresentationUniqueKeyGrouping(IQueryable<BaseItemEntity> dbQuery, JellyfinDbContext context, CancellationToken cancellationToken) private async Task<List<Guid>> ApplyPresentationUniqueKeyGrouping(IQueryable<BaseItemEntity> dbQuery, JellyfinDbContext context, CancellationToken cancellationToken)
{ {
// Get distinct presentation keys along with one ID per unique key // Get all IDs in sorted order FIRST (preserves the ORDER BY from ApplyOrder)
// This uses GROUP BY at the database level instead of in-memory DISTINCT var sortedIds = await dbQuery
var groupedByPresentationKeyIds = await dbQuery .Select(e => new { e.Id, e.PresentationUniqueKey })
.GroupBy(e => e.PresentationUniqueKey)
.Select(g => g.First().Id)
.ToListAsync(cancellationToken) .ToListAsync(cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
// Deduplicate by PresentationUniqueKey while preserving the sorted order
var seenKeys = new HashSet<string>();
var groupedByPresentationKeyIds = new List<Guid>();
foreach (var item in sortedIds)
{
if (seenKeys.Add(item.PresentationUniqueKey ?? string.Empty))
{
groupedByPresentationKeyIds.Add(item.Id);
}
}
return groupedByPresentationKeyIds; return groupedByPresentationKeyIds;
} }