aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Jellyfin.Server/Migrations/Routines/20260113230000_CleanupOrphanedExtras.cs43
-rw-r--r--Jellyfin.Server/Migrations/Routines/20260508120000_MergeDuplicateMusicArtists.cs40
-rw-r--r--Jellyfin.Server/Migrations/Routines/20260508130000_MergeDuplicatePeople.cs40
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)