Fix DatePlayed sorting performance
This commit is contained in:
@@ -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
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
+2
@@ -61,6 +61,8 @@ public class BaseItemConfiguration : IEntityTypeConfiguration<BaseItemEntity>
|
||||
builder.HasIndex(e => new { e.TopParentId, e.MediaType, e.IsVirtualItem, e.DateCreated });
|
||||
// resume
|
||||
builder.HasIndex(e => new { e.MediaType, e.TopParentId, e.IsVirtualItem, e.PresentationUniqueKey });
|
||||
// alternate versions of an item, e.g. resolving the played date of a version onto its primary
|
||||
builder.HasIndex(e => e.PrimaryVersionId);
|
||||
// sorted library queries (e.g., Series sorted by SortName)
|
||||
builder.HasIndex(e => new { e.Type, e.TopParentId, e.SortName });
|
||||
// NextUp: per-series episode ordering (index seek + range scan on season/episode)
|
||||
|
||||
+1812
File diff suppressed because it is too large
Load Diff
+27
@@ -0,0 +1,27 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Jellyfin.Server.Implementations.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddPrimaryVersionIdIndex : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItems_PrimaryVersionId",
|
||||
table: "BaseItems",
|
||||
column: "PrimaryVersionId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_PrimaryVersionId",
|
||||
table: "BaseItems");
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -15,7 +15,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.12");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.10");
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.AccessSchedule", b =>
|
||||
{
|
||||
@@ -373,6 +373,8 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
|
||||
b.HasIndex("PresentationUniqueKey");
|
||||
|
||||
b.HasIndex("PrimaryVersionId");
|
||||
|
||||
b.HasIndex("SeasonId");
|
||||
|
||||
b.HasIndex("SeriesId");
|
||||
|
||||
Reference in New Issue
Block a user