aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoshua M. Boniface <joshua@boniface.me>2026-06-21 23:06:45 -0400
committerJoshua M. Boniface <joshua@boniface.me>2026-06-21 23:31:25 -0400
commitb60c535c84a382d06eab5b0c38ee279103b06cf2 (patch)
treef952070278cdd9c02400d3aa08a278fba38e45b1
parent069eb40ebfdca4030d0c87d56f4398f6f5d1b55e (diff)
Add progress logging and batch deletion for logs
After resolving duplicates the migration deleted all items in one silent pass (per-id GetItemById plus a single DeleteItemsUnsafeFast), which looks hung for minutes on large libraries. Delete in batches of 500 and log progress per batch, which also avoids one oversized delete transaction.
-rw-r--r--Jellyfin.Server/Migrations/Routines/20260115120000_FixIncorrectOwnerIdRelationships.cs43
1 files changed, 31 insertions, 12 deletions
diff --git a/Jellyfin.Server/Migrations/Routines/20260115120000_FixIncorrectOwnerIdRelationships.cs b/Jellyfin.Server/Migrations/Routines/20260115120000_FixIncorrectOwnerIdRelationships.cs
index 0baf261a2e..e34182fd5d 100644
--- a/Jellyfin.Server/Migrations/Routines/20260115120000_FixIncorrectOwnerIdRelationships.cs
+++ b/Jellyfin.Server/Migrations/Routines/20260115120000_FixIncorrectOwnerIdRelationships.cs
@@ -136,19 +136,38 @@ public class FixIncorrectOwnerIdRelationships : IAsyncMigrationRoutine
if (allIdsToDelete.Count > 0)
{
- // Batch-resolve items for metadata path cleanup, then delete all at once
- var itemsToDelete = allIdsToDelete
- .Select(id => _libraryManager.GetItemById(id))
- .Where(item => item is not null)
- .ToList();
- _libraryManager.DeleteItemsUnsafeFast(itemsToDelete!);
-
- // Fall back to direct DB deletion for any items that couldn't be resolved via LibraryManager
- var deletedIds = itemsToDelete.Select(i => i!.Id).ToHashSet();
- var unresolvedIds = allIdsToDelete.Where(id => !deletedIds.Contains(id)).ToList();
- if (unresolvedIds.Count > 0)
+ _logger.LogInformation("Deleting {Count} duplicate database entries...", allIdsToDelete.Count);
+
+ // Delete in batches so progress is visible (item resolution and deletion can take a
+ // long time on large libraries) and so we never issue one massive delete transaction.
+ const int deleteBatchSize = 500;
+ var deletedSoFar = 0;
+ for (var offset = 0; offset < allIdsToDelete.Count; offset += deleteBatchSize)
{
- _persistenceService.DeleteItem(unresolvedIds);
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var batchIds = allIdsToDelete.GetRange(offset, Math.Min(deleteBatchSize, allIdsToDelete.Count - offset));
+
+ // Resolve items for metadata path cleanup, then delete this batch
+ var itemsToDelete = batchIds
+ .Select(id => _libraryManager.GetItemById(id))
+ .Where(item => item is not null)
+ .ToList();
+ if (itemsToDelete.Count > 0)
+ {
+ _libraryManager.DeleteItemsUnsafeFast(itemsToDelete!);
+ }
+
+ // Fall back to direct DB deletion for any items that couldn't be resolved via LibraryManager
+ var deletedIds = itemsToDelete.Select(i => i!.Id).ToHashSet();
+ var unresolvedIds = batchIds.Where(id => !deletedIds.Contains(id)).ToList();
+ if (unresolvedIds.Count > 0)
+ {
+ _persistenceService.DeleteItem(unresolvedIds);
+ }
+
+ deletedSoFar += batchIds.Count;
+ _logger.LogInformation("Deleting duplicates: {Deleted}/{Total} items", deletedSoFar, allIdsToDelete.Count);
}
}