diff options
| author | orut34iop <orut34iop@users.noreply.github.com> | 2026-09-15 11:16:55 -0400 |
|---|---|---|
| committer | Cody Robibero <cody@robibe.ro> | 2026-09-15 11:16:55 -0400 |
| commit | 5a3a5a26884254bfc7d12beff038ef8f1a89a3cf (patch) | |
| tree | 19af87112757f444adb0dc080262afc815c091f7 /Jellyfin.Server.Implementations | |
| parent | 7c7244d32fd9290989c523e3f344657d87cfbb7c (diff) | |
Backport pull request #18004 from jellyfin/release-12.z
Avoid full people scans and writes for unchanged credits
Original-merge: 63c81a9975c339a0757142cb69a318b50b85d216
Merged-by: crobibero <cody@robibe.ro>
Backported-by: Cody Robibero <cody@robibe.ro>
Diffstat (limited to 'Jellyfin.Server.Implementations')
| -rw-r--r-- | Jellyfin.Server.Implementations/Item/PeopleRepository.cs | 60 |
1 files changed, 51 insertions, 9 deletions
diff --git a/Jellyfin.Server.Implementations/Item/PeopleRepository.cs b/Jellyfin.Server.Implementations/Item/PeopleRepository.cs index b1acb277be..fcddc09ad9 100644 --- a/Jellyfin.Server.Implementations/Item/PeopleRepository.cs +++ b/Jellyfin.Server.Implementations/Item/PeopleRepository.cs @@ -127,18 +127,61 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I var distinctCredits = credits.DistinctBy(e => (e.LoweredName, e.PersonType, e.LoweredRole)).ToArray(); var distinctPersons = distinctCredits.DistinctBy(e => (e.LoweredName, e.PersonType)).ToArray(); - var personKeys = distinctPersons.Select(e => e.LoweredName + "-" + e.PersonType).ToArray(); using var context = _dbProvider.CreateDbContext(); + var existingMaps = context.PeopleBaseItemMap + .AsNoTracking() + .Include(e => e.People) + .Where(e => e.ItemId == itemId) + .ToList(); + + // Most library scans refresh unchanged local metadata. Avoid opening a write + // transaction when the item's people mappings, order and roles are unchanged. + var incomingCredits = distinctCredits + .Select((credit, index) => new + { + Key = (credit.LoweredName, credit.PersonType, credit.LoweredRole), + Role = credit.Person.Role, + ListOrder = index, + SortOrder = credit.Person.SortOrder + }) + .ToDictionary(e => e.Key); + var mappingsAreUnchanged = existingMaps.Count == incomingCredits.Count + && existingMaps.All(map => + incomingCredits.TryGetValue( + (map.People.Name.ToLowerInvariant(), map.People.PersonType ?? string.Empty, map.Role?.ToLowerInvariant() ?? string.Empty), + out var incoming) + && map.ListOrder == incoming.ListOrder + && map.SortOrder == incoming.SortOrder + && string.Equals(map.Role ?? string.Empty, incoming.Role, StringComparison.OrdinalIgnoreCase)); + + if (mappingsAreUnchanged) + { + return; + } + using var transaction = context.Database.BeginTransaction(); - var existingPersons = context.Peoples.Select(e => new + // The fast-path snapshot was read before acquiring the write transaction. Reload + // tracked mappings inside it so a concurrent refresh cannot leave stale credits. + existingMaps = context.PeopleBaseItemMap + .Include(e => e.People) + .Where(e => e.ItemId == itemId) + .ToList(); + + // Query each person type separately so SQLite can use IX_Peoples_NameLower. + // Combining the two fields into `lower(Name) || '-' || PersonType` forces a full + // scan of Peoples for every media item, which is prohibitive during a large import. + var existingPersons = new List<People>(); + foreach (var personTypeGroup in distinctPersons.GroupBy(e => e.PersonType, StringComparer.Ordinal)) { - item = e, - SelectionKey = e.Name.ToLower() + "-" + e.PersonType - }) - .Where(p => personKeys.Contains(p.SelectionKey)) - .Select(f => f.item) - .ToArray(); + var names = personTypeGroup + .Select(e => e.LoweredName) + .ToArray(); + + existingPersons.AddRange(context.Peoples + .Where(e => e.PersonType == personTypeGroup.Key && names.Contains(e.Name.ToLower())) + .ToArray()); + } var existingPersonKeys = existingPersons.Select(e => (e.Name.ToLowerInvariant(), e.PersonType ?? string.Empty)).ToHashSet(); @@ -157,7 +200,6 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I personsEntities.TryAdd((entity.Name.ToLowerInvariant(), entity.PersonType ?? string.Empty), entity); } - var existingMaps = context.PeopleBaseItemMap.Include(e => e.People).Where(e => e.ItemId == itemId).ToList(); var existingMapsByCredit = new Dictionary<(string LoweredName, string PersonType, string LoweredRole), PeopleBaseItemMap>(); foreach (var map in existingMaps) { |
