diff options
3 files changed, 79 insertions, 44 deletions
diff --git a/Jellyfin.Server/Migrations/Routines/20260113230000_CleanupOrphanedExtras.cs b/Jellyfin.Server/Migrations/Routines/20260113230000_CleanupOrphanedExtras.cs index f4dfa49068..e8eeb2da20 100644 --- a/Jellyfin.Server/Migrations/Routines/20260113230000_CleanupOrphanedExtras.cs +++ b/Jellyfin.Server/Migrations/Routines/20260113230000_CleanupOrphanedExtras.cs @@ -76,25 +76,36 @@ public class CleanupOrphanedExtras : IAsyncMigrationRoutine _logger.LogInformation("Found {Count} orphaned extras to remove", orphanedItemIds.Count); - // Batch-resolve items for metadata path cleanup, then delete all at once - var itemsToDelete = new List<BaseItem>(); - foreach (var itemId in orphanedItemIds) + // Resolve items for metadata path cleanup, then delete in batches so we never issue one + // massive delete transaction and progress stays visible on large libraries. + _logger.LogInformation("Deleting {Count} orphaned extras...", orphanedItemIds.Count); + const int deleteBatchSize = 500; + var deletedSoFar = 0; + for (var offset = 0; offset < orphanedItemIds.Count; offset += deleteBatchSize) { - itemsToDelete.Add(BaseItemMapper.DeserializeBaseItem( - new Database.Implementations.Entities.BaseItemEntity() - { - Id = itemId.Id, - Path = itemId.Path, - Type = itemId.Type - }, - _logger, - null, - true)!); - } + cancellationToken.ThrowIfCancellationRequested(); + + var batch = orphanedItemIds.GetRange(offset, Math.Min(deleteBatchSize, orphanedItemIds.Count - offset)); + var itemsToDelete = batch + .Select(itemId => BaseItemMapper.DeserializeBaseItem( + new Database.Implementations.Entities.BaseItemEntity() + { + Id = itemId.Id, + Path = itemId.Path, + Type = itemId.Type + }, + _logger, + null, + true)!) + .ToList(); - _libraryManager.DeleteItemsUnsafeFast(itemsToDelete); + _libraryManager.DeleteItemsUnsafeFast(itemsToDelete); + + deletedSoFar += batch.Count; + _logger.LogInformation("Deleting orphaned extras: {Deleted}/{Total}", deletedSoFar, orphanedItemIds.Count); + } - _logger.LogInformation("Successfully removed {Count} orphaned extras", itemsToDelete.Count); + _logger.LogInformation("Successfully removed {Count} orphaned extras", orphanedItemIds.Count); } } } diff --git a/Jellyfin.Server/Migrations/Routines/20260508120000_MergeDuplicateMusicArtists.cs b/Jellyfin.Server/Migrations/Routines/20260508120000_MergeDuplicateMusicArtists.cs index f598848465..bff6ebbfb0 100644 --- a/Jellyfin.Server/Migrations/Routines/20260508120000_MergeDuplicateMusicArtists.cs +++ b/Jellyfin.Server/Migrations/Routines/20260508120000_MergeDuplicateMusicArtists.cs @@ -182,23 +182,35 @@ public class MergeDuplicateMusicArtists : IAsyncMigrationRoutine // Resolve via LibraryManager so DeleteItemsUnsafeFast can also remove the // %MetadataPath%/artists/<Name> directories that the duplicate stubs left behind. // Fall back to the persistence service for any items the LibraryManager can't resolve. - var itemsToDelete = idsToDelete - .Select(id => _libraryManager.GetItemById(id)) - .Where(item => item is not null) - .ToList(); - if (itemsToDelete.Count > 0) + // Delete in batches so we never issue one massive delete transaction and progress stays visible. + _logger.LogInformation("Deleting {Count} duplicate MusicArtist records...", idsToDelete.Count); + const int deleteBatchSize = 500; + var deletedSoFar = 0; + for (var offset = 0; offset < idsToDelete.Count; offset += deleteBatchSize) { - _libraryManager.DeleteItemsUnsafeFast(itemsToDelete!); - } + cancellationToken.ThrowIfCancellationRequested(); - var deletedIds = itemsToDelete.Select(i => i!.Id).ToHashSet(); - var unresolvedIds = idsToDelete.Where(id => !deletedIds.Contains(id)).ToList(); - if (unresolvedIds.Count > 0) - { - _persistenceService.DeleteItem(unresolvedIds); - } + var batchIds = idsToDelete.GetRange(offset, Math.Min(deleteBatchSize, idsToDelete.Count - offset)); + + var itemsToDelete = batchIds + .Select(id => _libraryManager.GetItemById(id)) + .Where(item => item is not null) + .ToList(); + if (itemsToDelete.Count > 0) + { + _libraryManager.DeleteItemsUnsafeFast(itemsToDelete!); + } + + var deletedIds = itemsToDelete.Select(i => i!.Id).ToHashSet(); + var unresolvedIds = batchIds.Where(id => !deletedIds.Contains(id)).ToList(); + if (unresolvedIds.Count > 0) + { + _persistenceService.DeleteItem(unresolvedIds); + } - _logger.LogInformation("Removed {Count} duplicate MusicArtist records.", idsToDelete.Count); + deletedSoFar += batchIds.Count; + _logger.LogInformation("Deleting duplicate MusicArtist records: {Deleted}/{Total}", deletedSoFar, idsToDelete.Count); + } } } } diff --git a/Jellyfin.Server/Migrations/Routines/20260508130000_MergeDuplicatePeople.cs b/Jellyfin.Server/Migrations/Routines/20260508130000_MergeDuplicatePeople.cs index 10433599fa..f28c804d26 100644 --- a/Jellyfin.Server/Migrations/Routines/20260508130000_MergeDuplicatePeople.cs +++ b/Jellyfin.Server/Migrations/Routines/20260508130000_MergeDuplicatePeople.cs @@ -184,23 +184,35 @@ public class MergeDuplicatePeople : IAsyncMigrationRoutine // Resolve via LibraryManager so DeleteItemsUnsafeFast can also remove the // %MetadataPath%/People/<Letter>/<Name> directories the duplicate stubs left behind. - var itemsToDelete = idsToDelete - .Select(id => _libraryManager.GetItemById(id)) - .Where(item => item is not null) - .ToList(); - if (itemsToDelete.Count > 0) + // Delete in batches so we never issue one massive delete transaction and progress stays visible. + _logger.LogInformation("Deleting {Count} duplicate Person BaseItems...", idsToDelete.Count); + const int deleteBatchSize = 500; + var deletedSoFar = 0; + for (var offset = 0; offset < idsToDelete.Count; offset += deleteBatchSize) { - _libraryManager.DeleteItemsUnsafeFast(itemsToDelete!); - } + cancellationToken.ThrowIfCancellationRequested(); - var deletedIds = itemsToDelete.Select(i => i!.Id).ToHashSet(); - var unresolvedIds = idsToDelete.Where(id => !deletedIds.Contains(id)).ToList(); - if (unresolvedIds.Count > 0) - { - _persistenceService.DeleteItem(unresolvedIds); - } + var batchIds = idsToDelete.GetRange(offset, Math.Min(deleteBatchSize, idsToDelete.Count - offset)); + + var itemsToDelete = batchIds + .Select(id => _libraryManager.GetItemById(id)) + .Where(item => item is not null) + .ToList(); + if (itemsToDelete.Count > 0) + { + _libraryManager.DeleteItemsUnsafeFast(itemsToDelete!); + } + + var deletedIds = itemsToDelete.Select(i => i!.Id).ToHashSet(); + var unresolvedIds = batchIds.Where(id => !deletedIds.Contains(id)).ToList(); + if (unresolvedIds.Count > 0) + { + _persistenceService.DeleteItem(unresolvedIds); + } - _logger.LogInformation("Removed {Count} duplicate Person BaseItems.", idsToDelete.Count); + deletedSoFar += batchIds.Count; + _logger.LogInformation("Deleting duplicate Person BaseItems: {Deleted}/{Total}", deletedSoFar, idsToDelete.Count); + } } private async Task MergePeoplesRowsAsync(JellyfinDbContext context, CancellationToken cancellationToken) |
