aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShadowghost <Ghost_of_Stone@web.de>2026-07-23 22:38:45 +0200
committerShadowghost <Ghost_of_Stone@web.de>2026-07-23 22:38:45 +0200
commit95330223f49c6ba8b1d77fbe4e4dad4fc6ba9be9 (patch)
tree24cd5ff0d4de6976e8494ddfc7f33e9972f3301b
parentd4cddb8a5de9d3eec07f5e5f594d21d787655269 (diff)
Speedup migration
-rw-r--r--Jellyfin.Server/Migrations/Routines/20260723120000_RecomputeSeriesPresentationKey.cs76
1 files changed, 40 insertions, 36 deletions
diff --git a/Jellyfin.Server/Migrations/Routines/20260723120000_RecomputeSeriesPresentationKey.cs b/Jellyfin.Server/Migrations/Routines/20260723120000_RecomputeSeriesPresentationKey.cs
index 3542c580a8..60bb3fd1db 100644
--- a/Jellyfin.Server/Migrations/Routines/20260723120000_RecomputeSeriesPresentationKey.cs
+++ b/Jellyfin.Server/Migrations/Routines/20260723120000_RecomputeSeriesPresentationKey.cs
@@ -4,10 +4,12 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations;
using Jellyfin.Server.ServerSetupApp;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Server.Migrations.Routines;
@@ -21,18 +23,22 @@ internal class RecomputeSeriesPresentationKey : IAsyncMigrationRoutine
{
private readonly IStartupLogger<RecomputeSeriesPresentationKey> _logger;
private readonly ILibraryManager _libraryManager;
+ private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
/// <summary>
/// Initializes a new instance of the <see cref="RecomputeSeriesPresentationKey"/> class.
/// </summary>
/// <param name="logger">The startup logger.</param>
/// <param name="libraryManager">The library manager.</param>
+ /// <param name="dbProvider">The database context factory.</param>
public RecomputeSeriesPresentationKey(
IStartupLogger<RecomputeSeriesPresentationKey> logger,
- ILibraryManager libraryManager)
+ ILibraryManager libraryManager,
+ IDbContextFactory<JellyfinDbContext> dbProvider)
{
_logger = logger;
_libraryManager = libraryManager;
+ _dbProvider = dbProvider;
}
/// <inheritdoc />
@@ -40,57 +46,55 @@ internal class RecomputeSeriesPresentationKey : IAsyncMigrationRoutine
{
var series = _libraryManager.GetItemList(new InternalItemsQuery
{
- IncludeItemTypes = new[] { BaseItemKind.Series }
+ IncludeItemTypes = [BaseItemKind.Series]
}).OfType<Series>().ToArray();
_logger.LogInformation("Recomputing presentation unique key for {Count} series", series.Length);
- const int ProgressInterval = 500;
+ const int ProgressInterval = 250;
var sw = Stopwatch.StartNew();
var processed = 0;
var updated = 0;
- foreach (var item in series)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- if (++processed % ProgressInterval == 0)
- {
- _logger.LogInformation("Processed {Processed}/{Total} series - Updated: {Updated} - Time: {Elapsed}", processed, series.Length, updated, sw.Elapsed);
- }
- var oldKey = item.PresentationUniqueKey;
- var newKey = item.CreatePresentationUniqueKey();
- if (string.Equals(oldKey, newKey, StringComparison.Ordinal))
+ var dbContext = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
+ await using (dbContext.ConfigureAwait(false))
+ {
+ foreach (var item in series)
{
- continue;
- }
+ cancellationToken.ThrowIfCancellationRequested();
- item.PresentationUniqueKey = newKey;
- await item.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
+ if (++processed % ProgressInterval == 0)
+ {
+ _logger.LogInformation("Processed {Processed}/{Total} series - Updated: {Updated} - Time: {Elapsed}", processed, series.Length, updated, sw.Elapsed);
+ }
- // Seasons and episodes cache the series key in SeriesPresentationUniqueKey and are matched
- // to the series by it. Look them up by the old key (they still carry it) and re-point
- // them at the new key so they stay attached without waiting for the next scan.
- if (!string.IsNullOrEmpty(oldKey))
- {
- var children = _libraryManager.GetItemList(new InternalItemsQuery
+ var oldKey = item.PresentationUniqueKey;
+ var newKey = item.CreatePresentationUniqueKey();
+ if (string.Equals(oldKey, newKey, StringComparison.Ordinal))
{
- SeriesPresentationUniqueKey = oldKey,
- IncludeItemTypes = [BaseItemKind.Season, BaseItemKind.Episode]
- });
+ continue;
+ }
- foreach (var child in children)
+ // Write only the changed column instead of re-persisting the whole item.
+ var id = item.Id;
+ await dbContext.BaseItems
+ .Where(e => e.Id.Equals(id))
+ .ExecuteUpdateAsync(e => e.SetProperty(f => f.PresentationUniqueKey, newKey), cancellationToken)
+ .ConfigureAwait(false);
+
+ // Seasons and episodes cache the series key in SeriesPresentationUniqueKey and are matched
+ // to the series by it. Re-point every child still carrying the old key in a single set-based
+ // update so they stay attached without waiting for the next scan.
+ if (!string.IsNullOrEmpty(oldKey))
{
- if (child is IHasSeries hasSeries
- && !string.Equals(hasSeries.SeriesPresentationUniqueKey, newKey, StringComparison.Ordinal))
- {
- hasSeries.SeriesPresentationUniqueKey = newKey;
- await child.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
- }
+ await dbContext.BaseItems
+ .Where(e => e.SeriesPresentationUniqueKey == oldKey)
+ .ExecuteUpdateAsync(e => e.SetProperty(f => f.SeriesPresentationUniqueKey, newKey), cancellationToken)
+ .ConfigureAwait(false);
}
- }
- updated++;
+ updated++;
+ }
}
_logger.LogInformation("Recomputed presentation unique key for {Updated} of {Count} series in {Elapsed}", updated, series.Length, sw.Elapsed);