# Jellyfin Async Migration Plan ## ๐ŸŽฏ Objective Convert Jellyfin database operations from synchronous to asynchronous to enable PostgreSQL multiplexing and improve performance. ## ๐Ÿ“‹ Migration Phases ### Phase 1: Repository Interfaces (Breaking Changes) **Estimated Effort**: 2-3 weeks #### Files to Update: - `MediaBrowser.Controller\Persistence\IItemRepository.cs` - `MediaBrowser.Controller\Library\IUserManager.cs` - `MediaBrowser.Controller\Persistence\IUserDataRepository.cs` - All other repository interfaces #### Changes: ```csharp // BEFORE void DeleteItem(params IReadOnlyList ids); void SaveItems(IReadOnlyList items, CancellationToken cancellationToken); BaseItem RetrieveItem(Guid id); QueryResult GetItems(InternalItemsQuery filter); // AFTER Task DeleteItemAsync(IReadOnlyList ids, CancellationToken cancellationToken = default); Task SaveItemsAsync(IReadOnlyList items, CancellationToken cancellationToken = default); Task RetrieveItemAsync(Guid id, CancellationToken cancellationToken = default); Task> GetItemsAsync(InternalItemsQuery filter, CancellationToken cancellationToken = default); ``` ### Phase 2: Repository Implementations **Estimated Effort**: 3-4 weeks #### Files to Update: - `Jellyfin.Server.Implementations\Item\BaseItemRepository.cs` - `Jellyfin.Server.Implementations\Users\UserManager.cs` - All repository implementation classes #### Key Changes: ##### 1. SaveChanges โ†’ SaveChangesAsync ```csharp // BEFORE context.SaveChanges(); transaction.Commit(); // AFTER await context.SaveChangesAsync(cancellationToken); await transaction.CommitAsync(cancellationToken); ``` ##### 2. ExecuteDelete โ†’ ExecuteDeleteAsync ```csharp // BEFORE context.UserData.WhereOneOrMany(relatedItems, e => e.ItemId).ExecuteDelete(); // AFTER await context.UserData.WhereOneOrMany(relatedItems, e => e.ItemId) .ExecuteDeleteAsync(cancellationToken); ``` ##### 3. ExecuteUpdate โ†’ ExecuteUpdateAsync ```csharp // BEFORE context.UserData.ExecuteUpdate(e => e .SetProperty(f => f.RetentionDate, date)); // AFTER await context.UserData.ExecuteUpdateAsync(e => e .SetProperty(f => f.RetentionDate, date), cancellationToken); ``` ##### 4. ToArray/ToList โ†’ ToArrayAsync/ToListAsync ```csharp // BEFORE var items = context.BaseItems.Where(x => x.Id == id).ToArray(); // AFTER var items = await context.BaseItems .Where(x => x.Id == id) .ToArrayAsync(cancellationToken); ``` ##### 5. FirstOrDefault โ†’ FirstOrDefaultAsync ```csharp // BEFORE var item = context.BaseItems.FirstOrDefault(x => x.Id == id); // AFTER var item = await context.BaseItems .FirstOrDefaultAsync(x => x.Id == id, cancellationToken); ``` ##### 6. Any โ†’ AnyAsync ```csharp // BEFORE if (context.BaseItems.Any(x => x.Id == id)) // AFTER if (await context.BaseItems.AnyAsync(x => x.Id == id, cancellationToken)) ``` ##### 7. Count โ†’ CountAsync ```csharp // BEFORE var count = context.BaseItems.Count(); // AFTER var count = await context.BaseItems.CountAsync(cancellationToken); ``` ### Phase 3: Service Layer **Estimated Effort**: 4-6 weeks #### Files to Update: - `Emby.Server.Implementations\Library\LibraryManager.cs` - `Emby.Server.Implementations\Library\MediaSourceManager.cs` - All service classes that call repositories #### Pattern: ```csharp // BEFORE public BaseItem GetItem(Guid id) { return _itemRepository.RetrieveItem(id); } // AFTER public async Task GetItemAsync(Guid id, CancellationToken cancellationToken = default) { return await _itemRepository.RetrieveItemAsync(id, cancellationToken); } ``` ### Phase 4: API Controllers **Estimated Effort**: 3-4 weeks #### Files to Update: - `Jellyfin.Api\Controllers\ItemsController.cs` - `Jellyfin.Api\Controllers\UsersController.cs` - All API controller classes #### Pattern: ```csharp // BEFORE [HttpGet("{id}")] public ActionResult GetItem([FromRoute] Guid id) { var item = _libraryManager.GetItem(id); return item is null ? NotFound() : Ok(item); } // AFTER [HttpGet("{id}")] public async Task> GetItem( [FromRoute] Guid id, CancellationToken cancellationToken) { var item = await _libraryManager.GetItemAsync(id, cancellationToken); return item is null ? NotFound() : Ok(item); } ``` ### Phase 5: Background Services & Scheduled Tasks **Estimated Effort**: 2-3 weeks #### Files to Update: - `Emby.Server.Implementations\ScheduledTasks\*` - Background services using repositories #### Pattern: ```csharp // Ensure ExecuteAsync uses await for all database operations public async Task ExecuteAsync(CancellationToken cancellationToken) { await _itemRepository.UpdateInheritedValuesAsync(cancellationToken); } ``` ## ๐Ÿงช Testing Strategy ### 1. Unit Tests - Update all repository unit tests to async - Add tests for cancellation token handling - Verify no synchronous database calls remain ### 2. Integration Tests - Test full request pipeline (API โ†’ Service โ†’ Repository โ†’ Database) - Verify async behavior under load - Test cancellation scenarios ### 3. Performance Testing - Compare performance before/after migration - Test with multiplexing enabled vs disabled - Monitor connection pool usage ## ๐Ÿ“ฆ Migration Tools & Utilities ### Automated Detection Script ```powershell # Find synchronous EF Core operations Get-ChildItem -Recurse -Filter *.cs | Select-String -Pattern "\.ToList\(\)|\.ToArray\(\)|\.FirstOrDefault\(\)|\.SaveChanges\(\)|\.ExecuteDelete\(\)|\.ExecuteUpdate\(\)|\.Any\(\)|\.Count\(\)" | Where-Object { $_.Line -notmatch "Async" } | Select-Object Path, LineNumber, Line ``` ### Regex Patterns for Find/Replace ```regex # SaveChanges โ†’ SaveChangesAsync Find: \.SaveChanges\(\); Replace: await .SaveChangesAsync(cancellationToken); # ToArray โ†’ ToArrayAsync Find: \.ToArray\(\); Replace: await .ToArrayAsync(cancellationToken); # FirstOrDefault โ†’ FirstOrDefaultAsync Find: \.FirstOrDefault\( Replace: await .FirstOrDefaultAsync( ``` ## ๐Ÿšง Breaking Changes & Compatibility ### API Versioning - Consider maintaining v1 API with sync methods - Introduce v2 API with async methods - Deprecation timeline for v1 ### Plugin Compatibility - Plugins using sync methods will break - Provide migration guide for plugin developers - Consider compatibility shims for transition period ## ๐Ÿ“Š Effort Estimation Summary | Phase | Estimated Time | Risk Level | Priority | |-------|---------------|------------|----------| | Phase 1: Interfaces | 2-3 weeks | High | Critical | | Phase 2: Repositories | 3-4 weeks | High | Critical | | Phase 3: Services | 4-6 weeks | Medium | High | | Phase 4: Controllers | 3-4 weeks | Low | High | | Phase 5: Background | 2-3 weeks | Medium | Medium | | Testing | 2-3 weeks | High | Critical | | **Total** | **16-23 weeks** | **-** | **-** | ## โœ… Success Criteria 1. โœ… Zero synchronous database operations in hot paths 2. โœ… All repository methods are async 3. โœ… All API endpoints are async 4. โœ… PostgreSQL multiplexing can be enabled without errors 5. โœ… Performance improvements measurable 6. โœ… All tests passing 7. โœ… No increase in API latency ## ๐ŸŽฏ Quick Win: Start Small ### Proof of Concept (1-2 weeks) Convert a single, isolated feature to async: **Recommended Starting Point**: Activity Log Repository - Relatively simple - Limited dependencies - Easy to test - Low risk ```csharp // Convert ActivityLogRepository public interface IActivityLogRepository { Task GetAsync(int id, CancellationToken cancellationToken = default); Task> GetAllAsync( ActivityLogFilter filter, CancellationToken cancellationToken = default); Task CreateAsync(ActivityLog entry, CancellationToken cancellationToken = default); Task DeleteAsync(int id, CancellationToken cancellationToken = default); } ``` ## ๐Ÿ”ง PostgreSQL Multiplexing Benefits Once fully async, enable multiplexing: ```json { "Database": { "CustomProviderOptions": { "Options": [ { "Key": "multiplexing", "Value": "true" }, { "Key": "max-pool-size", "Value": "50" } ] } } } ``` **Expected Benefits**: - โšก 20-40% reduction in connection pool usage - โšก Better throughput under high concurrency - โšก Lower memory footprint - โšก Improved scalability ## ๐Ÿ“š Resources - [EF Core Async Guidance](https://learn.microsoft.com/en-us/ef/core/miscellaneous/async) - [Npgsql Multiplexing](https://www.npgsql.org/doc/connection-string-parameters.html#multiplexing) - [ASP.NET Core Async Best Practices](https://learn.microsoft.com/en-us/aspnet/core/performance/performance-best-practices) ## โš ๏ธ Risks & Mitigation | Risk | Impact | Mitigation | |------|--------|------------| | Breaking plugin API | High | Version API, provide migration guide | | Deadlocks from improper async | High | Code review, static analysis | | Performance regression | Medium | Extensive performance testing | | Testing gaps | High | Increase test coverage before migration | | Team learning curve | Medium | Training sessions, pair programming | ## ๐ŸŽ“ Team Onboarding ### Required Knowledge 1. C# async/await patterns 2. Task-based asynchronous programming 3. CancellationToken usage 4. EF Core async methods 5. Async enumeration (IAsyncEnumerable) ### Training Resources - Microsoft Learn: Async Programming - Jellyfin async migration guide (to be created) - Code review checklist - Pair programming sessions --- **Document Version**: 1.0 **Last Updated**: 2025-01-15 **Owner**: Database Team **Status**: Planning