diff options
| author | Shadowghost <Ghost_of_Stone@web.de> | 2026-07-23 22:38:45 +0200 |
|---|---|---|
| committer | Shadowghost <Ghost_of_Stone@web.de> | 2026-07-23 22:38:45 +0200 |
| commit | 95330223f49c6ba8b1d77fbe4e4dad4fc6ba9be9 (patch) | |
| tree | 24cd5ff0d4de6976e8494ddfc7f33e9972f3301b | |
| parent | d4cddb8a5de9d3eec07f5e5f594d21d787655269 (diff) | |
Speedup migration
| -rw-r--r-- | Jellyfin.Server/Migrations/Routines/20260723120000_RecomputeSeriesPresentationKey.cs | 76 |
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); |
