# 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<Guid> ids);
void SaveItems(IReadOnlyList<BaseItem> items, CancellationToken cancellationToken);
BaseItem RetrieveItem(Guid id);
QueryResult<BaseItem> GetItems(InternalItemsQuery filter);

// AFTER
Task DeleteItemAsync(IReadOnlyList<Guid> ids, CancellationToken cancellationToken = default);
Task SaveItemsAsync(IReadOnlyList<BaseItem> items, CancellationToken cancellationToken = default);
Task<BaseItem?> RetrieveItemAsync(Guid id, CancellationToken cancellationToken = default);
Task<QueryResult<BaseItem>> 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<BaseItem?> 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<BaseItemDto> GetItem([FromRoute] Guid id)
{
    var item = _libraryManager.GetItem(id);
    return item is null ? NotFound() : Ok(item);
}

// AFTER
[HttpGet("{id}")]
public async Task<ActionResult<BaseItemDto>> 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<ActivityLog?> GetAsync(int id, CancellationToken cancellationToken = default);
    Task<QueryResult<ActivityLog>> 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
