diff options
| author | Shadowghost <Ghost_of_Stone@web.de> | 2026-07-24 20:52:24 +0200 |
|---|---|---|
| committer | Shadowghost <Ghost_of_Stone@web.de> | 2026-07-25 19:09:39 +0200 |
| commit | f2f66606d7fd92d5552f7ecd178c339b59887e1d (patch) | |
| tree | df852c436d9e3231c4022b8bc19754ffaa67aaf7 /Jellyfin.Server.Implementations | |
| parent | 9aa79682bab9a18f87b0c13c776a19e3eb357335 (diff) | |
Fix DatePlayed sorting performance
Diffstat (limited to 'Jellyfin.Server.Implementations')
| -rw-r--r-- | Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs | 23 | ||||
| -rw-r--r-- | Jellyfin.Server.Implementations/Item/OrderMapper.cs | 27 |
2 files changed, 32 insertions, 18 deletions
diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs index f19df6259e..de25e9b763 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs @@ -560,16 +560,19 @@ public sealed partial class BaseItemRepository // Only in-progress siblings can eliminate a candidate: a version without progress has a NULL max LastPlayedDate, // which is never greater and never ties. Restricting the sibling scan to the in-progress set keeps this bounded by // the user's Continue Watching count instead of forcing a full BaseItems scan (COALESCE keys are non-indexable) per row. - baseQuery = baseQuery.Where(e => e.Type == seriesTypeName || !context.BaseItems - .Where(s => s.Id != e.Id - && inProgressIds.Contains(s.Id) - && (s.PrimaryVersionId ?? s.Id) == (e.PrimaryVersionId ?? e.Id)) - .Any(s => - inProgress.Where(su => su.ItemId == s.Id).Max(su => su.LastPlayedDate) - > inProgress.Where(eu => eu.ItemId == e.Id).Max(eu => eu.LastPlayedDate) - || (inProgress.Where(su => su.ItemId == s.Id).Max(su => su.LastPlayedDate) - == inProgress.Where(eu => eu.ItemId == e.Id).Max(eu => eu.LastPlayedDate) - && s.Id.CompareTo(e.Id) < 0))); + // Items in no version group at all have no sibling that could eliminate them, so short-circuit the scan for those. + baseQuery = baseQuery.Where(e => e.Type == seriesTypeName + || (e.PrimaryVersionId == null && !context.BaseItems.Any(a => a.PrimaryVersionId == e.Id)) + || !context.BaseItems + .Where(s => s.Id != e.Id + && inProgressIds.Contains(s.Id) + && (s.PrimaryVersionId ?? s.Id) == (e.PrimaryVersionId ?? e.Id)) + .Any(s => + inProgress.Where(su => su.ItemId == s.Id).Max(su => su.LastPlayedDate) + > inProgress.Where(eu => eu.ItemId == e.Id).Max(eu => eu.LastPlayedDate) + || (inProgress.Where(su => su.ItemId == s.Id).Max(su => su.LastPlayedDate) + == inProgress.Where(eu => eu.ItemId == e.Id).Max(eu => eu.LastPlayedDate) + && s.Id.CompareTo(e.Id) < 0))); } else { diff --git a/Jellyfin.Server.Implementations/Item/OrderMapper.cs b/Jellyfin.Server.Implementations/Item/OrderMapper.cs index aac85d0131..25ad81ec6c 100644 --- a/Jellyfin.Server.Implementations/Item/OrderMapper.cs +++ b/Jellyfin.Server.Implementations/Item/OrderMapper.cs @@ -29,19 +29,30 @@ public static class OrderMapper /// <returns>Func to be executed later for sorting query.</returns> public static Expression<Func<BaseItemEntity, object?>> MapOrderByField(ItemSortBy sortBy, InternalItemsQuery query, JellyfinDbContext jellyfinDbContext) { + if (sortBy == ItemSortBy.DatePlayed) + { + // An item's played date is the newest of its own progress and that of its alternate versions, + // which track progress under their own ids. Matching both in one predicate ORs them together, + // which no index can serve: the user's whole UserData table gets scanned per sorted row. + // Two indexed lookups combined by MAX cost a seek each instead. + var userData = query.User is null + ? jellyfinDbContext.UserData + : jellyfinDbContext.UserData.Where(w => w.UserId == query.User.Id); + + return e => userData + .Where(w => w.ItemId == e.Id) + .Select(w => w.LastPlayedDate) + .Concat(userData + .Where(w => w.Item!.PrimaryVersionId == e.Id) + .Select(w => w.LastPlayedDate)) + .Max(); + } + return (sortBy, query.User) switch { (ItemSortBy.AirTime, _) => e => e.SortName, (ItemSortBy.Runtime, _) => e => e.RunTimeTicks, (ItemSortBy.Random, _) => e => EF.Functions.Random(), - (ItemSortBy.DatePlayed, not null) => e => - jellyfinDbContext.UserData - .Where(w => w.UserId == query.User.Id && (w.ItemId == e.Id || w.Item!.PrimaryVersionId == e.Id)) - .Max(f => f.LastPlayedDate), - (ItemSortBy.DatePlayed, null) => e => - jellyfinDbContext.UserData - .Where(w => w.ItemId == e.Id || w.Item!.PrimaryVersionId == e.Id) - .Max(f => f.LastPlayedDate), (ItemSortBy.PlayCount, _) => e => e.UserData!.Where(f => f.UserId.Equals(query.User!.Id)).OrderBy(f => f.CustomDataKey).FirstOrDefault()!.PlayCount, (ItemSortBy.IsFavoriteOrLiked, _) => e => e.UserData!.Where(f => f.UserId.Equals(query.User!.Id)).OrderBy(f => f.CustomDataKey).Select(f => (bool?)f.IsFavorite).FirstOrDefault() ?? false, (ItemSortBy.IsFolder, _) => e => e.IsFolder, |
