Add SQL query patterns documentation and Linux package build scripts
- Created `TV_SHOWS_SQL_QUERY_PATTERNS.md` to document SQL query patterns for TV shows, including performance issues and missing indexes. - Added `README.md` for Linux package building, detailing steps for creating Debian and Red Hat packages. - Implemented build scripts for Debian and Red Hat, including service files and post-installation hooks. - Added necessary scripts for managing Jellyfin service lifecycle on both Debian and Red Hat systems. - Included package specifications and installation instructions for both distributions.
This commit is contained in:
@@ -450,14 +450,31 @@ public sealed class BaseItemRepository
|
||||
// Apply ordering before grouping so we get the right items
|
||||
dbQuery = ApplyOrder(dbQuery, filter, context);
|
||||
|
||||
// Get IDs only, without DistinctBy to avoid translation errors
|
||||
var allIds = await dbQuery
|
||||
.Select(e => new { e.Id, e.PresentationUniqueKey, SeriesPresentationUniqueKey = e.TvExtras!.SeriesPresentationUniqueKey })
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
// Optimize: Use database-level deduplication to avoid loading all items into memory
|
||||
var enableGrouping = EnableGroupByPresentationUniqueKey(filter);
|
||||
List<Guid> filteredIds;
|
||||
|
||||
if (enableGrouping && filter.GroupBySeriesPresentationUniqueKey)
|
||||
{
|
||||
// Use database-level grouping for TV Shows to avoid loading all episodes into memory
|
||||
filteredIds = await ApplySeriesGroupingAtDatabaseLevel(dbQuery, context, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else if (enableGrouping)
|
||||
{
|
||||
// Use database-level grouping for movies, videos, etc. (by PresentationUniqueKey) to avoid loading duplicates into memory
|
||||
filteredIds = await ApplyPresentationUniqueKeyGrouping(dbQuery, context, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get IDs only, without DistinctBy to avoid translation errors
|
||||
var allIds = await dbQuery
|
||||
.Select(e => new { e.Id, e.PresentationUniqueKey, SeriesPresentationUniqueKey = e.TvExtras!.SeriesPresentationUniqueKey })
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// Apply grouping/distinct in memory
|
||||
var filteredIds = ApplyGroupingInMemory(allIds, filter);
|
||||
// Apply grouping/distinct in memory
|
||||
filteredIds = ApplyGroupingInMemory(allIds, filter);
|
||||
}
|
||||
|
||||
// Apply paging to IDs
|
||||
var pagedIds = ApplyPagingToIds(filteredIds, filter);
|
||||
@@ -502,24 +519,41 @@ public sealed class BaseItemRepository
|
||||
// Apply ordering first, before grouping
|
||||
dbQuery = ApplyOrder(dbQuery, filter, context);
|
||||
|
||||
// Get IDs and keys to memory WITHOUT DistinctBy (which can't be translated)
|
||||
var itemsWithKeys = await dbQuery
|
||||
.Select(e => new
|
||||
{
|
||||
e.Id,
|
||||
e.PresentationUniqueKey,
|
||||
SeriesPresentationUniqueKey = e.TvExtras!.SeriesPresentationUniqueKey
|
||||
})
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
// Optimize: Use database-level deduplication to avoid loading all items into memory
|
||||
var enableGrouping = EnableGroupByPresentationUniqueKey(filter);
|
||||
List<Guid> groupedIds;
|
||||
|
||||
if (itemsWithKeys.Count == 0)
|
||||
if (enableGrouping && filter.GroupBySeriesPresentationUniqueKey)
|
||||
{
|
||||
return Array.Empty<BaseItemDto>();
|
||||
// Use database-level grouping for TV Shows to avoid loading all episodes into memory
|
||||
groupedIds = await ApplySeriesGroupingAtDatabaseLevel(dbQuery, context, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else if (enableGrouping)
|
||||
{
|
||||
// Use database-level grouping for movies, videos, etc. (by PresentationUniqueKey) to avoid loading duplicates into memory
|
||||
groupedIds = await ApplyPresentationUniqueKeyGrouping(dbQuery, context, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get IDs and keys to memory WITHOUT DistinctBy (which can't be translated)
|
||||
var itemsWithKeys = await dbQuery
|
||||
.Select(e => new
|
||||
{
|
||||
e.Id,
|
||||
e.PresentationUniqueKey,
|
||||
SeriesPresentationUniqueKey = e.TvExtras!.SeriesPresentationUniqueKey
|
||||
})
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// Apply grouping in memory
|
||||
var groupedIds = ApplyGroupingInMemory(itemsWithKeys, filter);
|
||||
if (itemsWithKeys.Count == 0)
|
||||
{
|
||||
return Array.Empty<BaseItemDto>();
|
||||
}
|
||||
|
||||
// Apply grouping in memory
|
||||
groupedIds = ApplyGroupingInMemory(itemsWithKeys, filter);
|
||||
}
|
||||
|
||||
// Apply paging to IDs
|
||||
var pagedIds = ApplyPagingToIds(groupedIds, filter);
|
||||
@@ -852,6 +886,42 @@ public sealed class BaseItemRepository
|
||||
return filtered.Select(e => (Guid)idProp.GetValue(e)!).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private async Task<List<Guid>> ApplySeriesGroupingAtDatabaseLevel(IQueryable<BaseItemEntity> 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)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return groupedBySeriesIds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private async Task<List<Guid>> ApplyPresentationUniqueKeyGrouping(IQueryable<BaseItemEntity> 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)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return groupedByPresentationKeyIds;
|
||||
}
|
||||
|
||||
private List<Guid> ApplyPagingToIds(List<Guid> ids, InternalItemsQuery filter)
|
||||
{
|
||||
IEnumerable<Guid> paged = ids;
|
||||
|
||||
Reference in New Issue
Block a user