43 KiB
Risk Management Strategies for .NET 11.0 Preview Migration
Based on the comprehensive plan I created, here's a detailed explanation of the risk management strategies:
🎯 Overall Risk Philosophy
The migration employs a defense-in-depth approach with three layers of protection:
- Preventive Controls - Stop problems before they occur (bottom-up strategy, tier ordering)
- Detective Controls - Identify problems early (extensive testing at each tier)
- Corrective Controls - Fix or rollback when problems occur (contingency plans, git rollback)
📊 Risk Classification System
Four-Tier Risk Model
| Risk Level | Impact Scope | Mitigation Priority | Example |
|---|---|---|---|
| CRITICAL | Entire solution | Highest | .NET 11.0 preview instability, Database providers |
| HIGH | Multiple projects | High | SkiaSharp native interop, Main application |
| MEDIUM | Single/few projects | Medium | API compatibility, Networking |
| LOW | Limited scope | Standard | Test projects, Utility libraries |
🚨 Critical Risk Management
Risk #1: .NET 11.0 Preview Framework Instability
Why Critical: Affects entire solution; preview = not production-ready
Mitigation Strategies:
-
Early Detection (Bottom-Up Strategy)
- Tier 2 is the canary tier - first to use .NET 11.0
- Issues discovered in Tier 2 affect only 3 projects, not 40
- Can assess viability before committing entire solution
-
Progressive Exposure
- Each tier tests more functionality
- Build confidence incrementally
- Learn .NET 11.0 behavior patterns early
-
Pause Points
- After Tier 2: Assess preview framework stability
- After Tier 6: Database providers validated (critical milestone)
- After Tier 11: Application functional
-
Rollback Capability
- Git branch isolation (
upgrade-to-NET11) - Can revert entire migration with single git operation
- Alternative target ready (.NET 10.0 LTS)
- Git branch isolation (
-
Monitoring
- Track .NET 11.0 preview announcements
- Monitor GitHub issues for known problems
- Stay connected with .NET community
Decision Triggers:
- ✅ Continue if Tier 2 succeeds with acceptable issues
- ⚠️ Pause if critical bugs discovered
- 🛑 Abort if blocking issues with no workaround
Risk #2: Database Provider Compatibility (Tier 6)
Why Critical: All data access depends on these; application unusable if broken
Mitigation Strategies:
-
Dedicated Tier (Tier 6)
- Isolated focus on database providers only
- No mixing with other concerns
- Full attention on validation
-
Extensive Testing Regime
✓ Connection tests (pooling, SSL, timeouts)
✓ Migration tests (existing migrations, upgrades)
✓ CRUD tests (all operations)
✓ Query translation tests (LINQ to SQL)
✓ Transaction tests (commit, rollback)
✓ Performance tests (baseline comparison)
✓ Concurrency tests (locking, isolation)
✓ Edge case tests (nulls, large datasets, errors)
-
Dual Provider Validation
- Test both PostgreSQL AND SQLite
- Ensure both work independently
- Validate switching between providers
- Real-world scenario testing
-
Performance Baseline
- Document .NET 9.0 query performance
- Compare .NET 11.0 against baseline
- Acceptable: <10% regression
- Red flag: >20% regression (investigate or pause)
-
GO/NO-GO Decision Point
- Tier 6 is the primary gate
- Must pass ALL criteria before proceeding
- If fails: entire migration may pause
- Options:
- Report to Npgsql/EF Core teams
- Wait for provider fixes
- Implement workarounds (if feasible)
- Switch to .NET 10.0 LTS
Why This Works:
- Catches database issues before 90% of codebase migrated
- Business logic (Tiers 8+) depends on stable database layer
- Prevents cascading failures up the stack
🎯 High Risk Management
Risk #3: Media Processing (SkiaSharp) - Tier 8
Challenge: Native library interop may break with .NET 11.0
Mitigation:
-
Native Library Verification
- Test library loading explicitly
- Verify cross-platform compatibility (Linux primary)
- Validate font rendering (HarfBuzz)
-
Functional Testing
- Image processing workflows
- Thumbnail generation
- Format conversions
-
Contingency
- Check for SkiaSharp .NET 11.0 updates
- Report issues to SkiaSharp maintainers
- May need to wait for library updates
- Can isolate failure to drawing subsystem
Risk #4: Main Application (Jellyfin.Server) - Tier 11
Challenge: Entry point where all features converge
Mitigation:
-
Foundation First
- Tiers 2-10 stable before reaching application
- Issues isolated to lower tiers
- Less debugging needed at application level
-
Comprehensive Smoke Testing
✓ Application starts
✓ Web UI loads
✓ Authentication works
✓ Library browsing
✓ Playback initiates
✓ API responds
- Staged Validation
- Build validation first
- Startup validation next
- Feature validation last
🔄 The Bottom-Up Strategy as Risk Management
Why Bottom-Up Reduces Risk
- Failure Blast Radius Control
Tier 2 failure: Affects 3 projects only
Tier 6 failure: Affects 6 projects (but caught before 90% of solution)
Tier 11 failure: All lower tiers proven stable (easier debugging)
vs. Top-Down:
Tier 11 failure first: 9 dependencies could be causing it (hard to debug)
- Progressive Validation
- Each tier includes test projects
- Lower tiers validated by higher tier tests
- Cumulative confidence builds
- Test pyramid:
Tier 13: Full integration tests (top)
Tier 12: Integration tests
Tier 9: API + business tests
Tier 5: Model tests
Tier 3: Component tests (bottom)
-
Incremental Learning
- Tier 2 teaches .NET 11.0 behavior
- Lessons applied to Tiers 3-13
- Pattern recognition for issues
- Team builds expertise as they progress
-
Flexible Rollback
- Can rollback at any tier
- Only lose work from current tier
- Not "all or nothing"
🛡️ Mitigation by Tier
Tier Completion Criteria (Safety Gates)
Each tier must pass criteria before next tier starts:
| Criterion | Purpose | Risk Mitigated |
|---|---|---|
| Build Success | No compilation errors | Breaking API changes |
| Zero Warnings | Code quality maintained | Technical debt accumulation |
| 100% Test Pass | Functionality intact | Regression bugs |
| Package Health | Dependencies resolved | Compatibility conflicts |
| Performance Check | Speed acceptable | Performance degradation |
| Manual Review | Human oversight | Subtle issues missed by automation |
If ANY criterion fails: Do not proceed. Fix or rollback tier.
📋 Rollback Strategy (Corrective Control)
When to Rollback
Automatic Triggers:
- ❌ Tier 2-4 critical failures (preview framework issues)
- ❌ Tier 6 database provider failure (GO/NO-GO gate)
- ❌ >20% test failures in any tier
- ❌ >30% performance degradation
- ❌ Blocking preview bugs with no workaround
- ❌ Security vulnerabilities in preview framework
How to Rollback
Git-Based Rollback (Fast & Clean):
# Option 1: Revert specific tier
git revert <tier-commit-sha>
# Option 2: Reset entire migration
git reset --hard <pre-migration-commit>
# Option 3: Switch to .NET 10.0 LTS
git checkout -b upgrade-to-NET10
# Adjust target framework to net10.0
Tier-Level Rollback:
- Revert only failed tier
- Keep lower tiers on .NET 11.0
- Analyze and fix issues
- Retry tier
Solution-Level Rollback:
- Complete revert to .NET 9.0
- Document .NET 11.0 issues discovered
- Switch to .NET 10.0 LTS (stable alternative)
📊 Risk Monitoring Dashboard
Key Metrics to Track
| Metric | Baseline (.NET 9.0) | Threshold | Action if Exceeded |
|---|---|---|---|
| Build Errors | 0 | 0 | Fix immediately |
| Test Pass Rate | 100% | 100% | Fix immediately |
| Query Performance | [Baseline] | +10% | Investigate |
| Startup Time | [Baseline] | +20% | Profile & optimize |
| Memory Usage | [Baseline] | +15% | Profile for leaks |
| API Response | [Baseline] | +15% | Investigate |
Continuous Monitoring
Per-Tier:
- Build time
- Test execution time
- Package restore time
- Compiler warnings count
Application-Level (Tier 11+):
- Startup time
- Memory consumption
- API latency
- Database query time
🎓 Risk Acceptance
Accepted Risks
These risks are knowingly accepted due to preview framework requirement:
✅ Minor preview instability - Expected; can work around
✅ Performance variations - Preview may be unoptimized; acceptable if <threshold
✅ Incomplete features - Can defer feature usage or wait for release
✅ Breaking changes between previews - Willing to adapt
NOT Accepted
❌ Critical runtime bugs - Must rollback
❌ Data corruption - Must rollback
❌ Security vulnerabilities - Must rollback
❌ Showstopper bugs - Must pause/rollback
🔍 Risk Communication
Stakeholder Updates
Frequency: After each critical tier
Tier 2: "Preview framework assessment complete - [GO/NO-GO]"
Tier 6: "Database providers validated - [GO/NO-GO]"
Tier 11: "Application functional - [GO/NO-GO]"
Tier 13: "Migration complete - ready for final review"
Issue Documentation:
- All issues encountered logged
- Workarounds documented
- Performance metrics recorded
- Lessons learned captured
💡 Key Risk Management Principles
1. Fail Fast, Fail Small
- Tier 2 (3 projects) fails fast
- Not Tier 11 (40 projects) failing late
2. Test Everything, Trust Nothing
- 100% test pass rate required
- Manual validation at critical points
- Performance baselines enforced
3. Reversibility
- Every change can be undone
- Git history clean
- Rollback procedures tested
4. Progressive Commitment
- Tier-by-tier investment
- Can pause at any tier
- Not "all in" upfront
5. Transparency
- All risks documented
- All issues logged
- All metrics tracked
📈 Success Factors
The risk management strategy succeeds because:
- ✅ Structural: Bottom-up architecture naturally limits blast radius
- ✅ Procedural: Strict tier completion criteria prevent advancement with issues
- ✅ Technical: Comprehensive testing catches problems early
- ✅ Organizational: Clear decision points and rollback triggers
- ✅ Cultural: Accepting that rollback is success (prevented larger failure)
🎯 Summary
This risk management strategy transforms a high-risk preview framework migration into a controlled, incremental process where:
- Risks are identified and classified upfront
- Critical risks get dedicated focus (Tier 6)
- Issues discovered early (Tier 2 canary)
- Progressive validation builds confidence
- Rollback always available
- Decision points clear
- Success defined explicitly
Result: A complex, high-risk migration becomes manageable through structured risk mitigation.
Tier 6: Database Providers & Naming - Detailed Plan
⚠️ CRITICAL TIER ALERT
This is the most critical tier in the entire migration. Tier 6 represents a GO/NO-GO decision point for the entire .NET 11.0 upgrade.
📊 Tier Overview
| Attribute | Details |
|---|---|
| Projects | 3 projects |
| Complexity | High |
| Risk Level | CRITICAL |
| Effort | High |
| Dependencies | Tiers 2-5 (all lower infrastructure) |
| Blocks | Tiers 7-13 (all higher tiers) |
Projects in Tier 6
- Emby.Naming - File/folder naming logic (Low Risk)
- Jellyfin.Database.Providers.Postgres - PostgreSQL EF Core provider ⚠️ (CRITICAL)
- Jellyfin.Database.Providers.Sqlite - SQLite EF Core provider ⚠️ (CRITICAL)
🎯 Why Tier 6 is Critical
Impact Scope
- All data access throughout Jellyfin depends on these providers
- Application unusable if database providers fail
- 50+ projects (Tiers 7-13) depend on stable database layer
Preview Framework Risks
- Npgsql.EntityFrameworkCore.PostgreSQL 11.0.0-preview.1 - Preview provider
- Microsoft.EntityFrameworkCore.Sqlite 11.0.0-preview.1 - Preview EF provider
- Unknown behavior in .NET 11.0 preview environment
Failure Consequences
If Tier 6 fails:
- Cannot proceed to business logic (Tier 8)
- Cannot proceed to API (Tier 9)
- Cannot proceed to application (Tier 11)
- Entire migration may need to pause or revert
📋 Project 1: Emby.Naming
Quick Profile
Risk: Low (naming logic only)
Complexity: Low
Migration: Straightforward
Current State
- Framework: .NET 9.0
- Type: Class Library
- Dependencies: MediaBrowser.Model (Tier 4), MediaBrowser.Common (Tier 5), Jellyfin.CodeAnalysis
- Used By:
- MediaBrowser.Controller (Tier 7)
- Emby.Server.Implementations (Tier 10)
- Jellyfin.Naming.Tests (Tier 7)
Target State
- Framework: .NET 11.0
- No package changes required
Migration Steps
- Update Project File
<TargetFramework>net11.0</TargetFramework>
-
Build and Validate
- Build succeeds without errors/warnings
- Naming pattern logic compiles
- File/folder naming utilities unchanged
-
Validation
- Will be tested by Jellyfin.Naming.Tests in Tier 7
- Focus: Ensure naming logic compiles cleanly
Expected Changes
✅ None expected - Simple file naming logic with minimal BCL usage
🚨 Project 2: Jellyfin.Database.Providers.Postgres
⚠️ CRITICAL DATABASE PROVIDER
This is the primary database provider for Jellyfin in production deployments.
Current State
- Framework: .NET 9.0
- Type: Class Library
- Dependencies:
- MediaBrowser.Common (Tier 5)
- Jellyfin.Database.Implementations (Tier 2)
- Jellyfin.CodeAnalysis
- Used By:
- Jellyfin.Server (Tier 11) - Main application
- Jellyfin.Server.Implementations (Tier 8)
Package Status
| Package | Current Version | Status |
|---|---|---|
| Npgsql.EntityFrameworkCore.PostgreSQL | 11.0.0-preview.1 | ✅ Already upgraded (PREVIEW) |
⚠️ Critical Note: This is a preview provider for a preview framework - double preview risk.
Migration Steps
Step 1: Prerequisites
Before starting migration, ensure:
- PostgreSQL database server available for testing
- Test database with representative Jellyfin schema
- User data
- Media library metadata
- Playback history
- Configuration data
- .NET 9.0 performance baseline established
- Query execution times
- Connection pool behavior
- Memory usage patterns
- Connection string configuration ready
- Database backup created (for safety)
Step 2: Framework Update
Update Jellyfin.Database.Providers.Postgres.csproj:
<TargetFramework>net11.0</TargetFramework>
No package updates required - already on 11.0.0-preview.1
Step 3: Expected Breaking Changes
Assessment says: ✅ None identified
Reality Check ⚠️ - Monitor for:
| Potential Issue | Description | Impact |
|---|---|---|
| Query Translation Changes | EF Core 11.0 may translate LINQ differently | SQL queries may fail or behave differently |
| Npgsql Behavior Changes | Provider-specific changes in preview | Connection, pooling, or query issues |
| Connection Pooling | Pool management changes | Performance degradation or connection leaks |
| Transaction Handling | Transaction isolation or rollback changes | Data integrity issues |
| Migration Compatibility | Existing migrations may not apply cleanly | Schema drift or migration failures |
Step 4: Code Modifications
Expected: None required
Be Prepared For:
// Potential DbContext configuration adjustments
services.AddDbContext<JellyfinDbContext>(options =>
{
options.UseNpgsql(connectionString, npgsqlOptions =>
{
// May need new configuration for .NET 11.0
npgsqlOptions.CommandTimeout(30);
npgsqlOptions.EnableRetryOnFailure(3);
// Check for new preview-specific options
});
});
// Potential connection string format changes
// Old: "Host=localhost;Database=jellyfin;..."
// New: May need adjustments for preview provider
// Provider-specific query hints may need updates
// Check if any raw SQL or provider-specific code needs changes
Step 5: EXTENSIVE Testing Strategy
This tier requires the most comprehensive testing of any tier.
5.1 Build Validation
Objective: Ensure compilation success before runtime testing
- Project builds without errors
- Project builds without warnings
- Code analysis passes (StyleCop, analyzers)
- DbContext registration compiles
- No obsolete API warnings
- NuGet packages restore successfully
Tools: dotnet build, Visual Studio Error List
5.2 Database Connection Tests
Objective: Verify provider can connect to PostgreSQL
Test Cases:
| Test | Validation | Pass Criteria |
|---|---|---|
| Connection String Parsing | Provider parses connection string correctly | No exceptions |
| Database Connection | Can establish connection | Connection state = Open |
| Connection Pooling | Pool creates/reuses connections | Pool metrics normal |
| SSL/TLS Connections | Encrypted connections work (if used) | Secure connection established |
| Connection Timeouts | Timeout handling works | Proper timeout exceptions |
| Authentication | User/password auth works | Successful authentication |
| Connection Closure | Connections close properly | No leaked connections |
Test Script Example:
[Fact]
public async Task PostgreSQL_CanConnect()
{
var options = new DbContextOptionsBuilder<JellyfinDbContext>()
.UseNpgsql(connectionString)
.Options;
await using var context = new JellyfinDbContext(options);
// Should not throw
var canConnect = await context.Database.CanConnectAsync();
Assert.True(canConnect);
}
5.3 Migration Tests
Objective: Ensure existing database migrations still work
Critical Tests:
-
Existing Migrations Valid
- All existing migrations compile
- Migration files unchanged
- No breaking changes in migration code
-
Apply to Empty Database
- Can create database from scratch
- All migrations apply in order
- Final schema matches expected
dotnet ef database update --connection "..."
-
Upgrade from Previous Version
- Can upgrade existing .NET 9.0 database
- Data preserved during upgrade
- No data loss or corruption
-
Migration History Table
- __EFMigrationsHistory table accessible
- History records correct
- Can query migration status
-
Schema Drift Detection
- No schema drift detected
- Database matches model
dotnet ef migrations has-pending-model-changes
Rollback Test:
- Can rollback migrations if needed
- Database returns to previous state
5.4 CRUD Operations Tests
Objective: Verify basic data operations work correctly
Test Matrix:
| Operation | Test Scenario | Validation |
|---|---|---|
| INSERT | Add new records | Records created with correct IDs |
| SELECT | Query existing records | Correct data returned |
| UPDATE | Modify existing records | Changes persisted |
| DELETE | Remove records | Records removed from database |
| Bulk INSERT | Add multiple records | All records created efficiently |
| Bulk UPDATE | Modify multiple records | All updates applied |
| Concurrent Operations | Multiple simultaneous operations | No conflicts or data corruption |
Example Test:
[Fact]
public async Task PostgreSQL_CRUD_Works()
{
await using var context = CreateContext();
// INSERT
var item = new BaseItem { Id = Guid.NewGuid(), Name = "Test" };
context.BaseItems.Add(item);
await context.SaveChangesAsync();
// SELECT
var retrieved = await context.BaseItems.FindAsync(item.Id);
Assert.NotNull(retrieved);
Assert.Equal("Test", retrieved.Name);
// UPDATE
retrieved.Name = "Updated";
await context.SaveChangesAsync();
// DELETE
context.BaseItems.Remove(retrieved);
await context.SaveChangesAsync();
// Verify deleted
var deleted = await context.BaseItems.FindAsync(item.Id);
Assert.Null(deleted);
}
5.5 Query Translation Tests
Objective: Ensure LINQ queries translate to correct PostgreSQL SQL
Critical Query Patterns (from Jellyfin codebase):
-
Simple Queries
- Basic WHERE clauses
- ORDER BY clauses
- TOP/LIMIT clauses
-
Complex Queries
- JOINs (inner, left, right)
- GROUP BY with aggregations
- Subqueries
- DISTINCT operations
-
Navigation Properties
- Eager loading (
.Include()) - Explicit loading (
.Load()) - Lazy loading (if enabled)
- Select projection with nested objects
- Eager loading (
-
Special Operations
- String operations (Contains, StartsWith)
- Date/time operations
- Null checks and coalescing
- Case-insensitive comparisons
Example Complex Query Test:
[Fact]
public async Task PostgreSQL_ComplexQuery_Translates()
{
await using var context = CreateContext();
// Complex query from BaseItemRepository
var query = context.BaseItems
.Where(e => e.Type == "Movie")
.Include(e => e.Images)
.Include(e => e.Provider)
.Where(e => e.DateCreated >= DateTime.UtcNow.AddDays(-30))
.GroupBy(e => e.ProductionYear)
.Select(g => new { Year = g.Key, Count = g.Count() })
.OrderByDescending(x => x.Count);
// Should not throw during translation
var results = await query.ToListAsync();
// Verify results make sense
Assert.NotNull(results);
}
5.6 Transaction Tests
Objective: Ensure transaction handling works correctly
| Test | Scenario | Expected Behavior |
|---|---|---|
| Explicit Transactions | Begin/commit transaction | Changes persisted |
| Transaction Rollback | Begin/rollback transaction | Changes reverted |
| Nested Transactions | Transaction within transaction | Handled correctly (if supported) |
| Distributed Transactions | Cross-database transactions | Works if used in Jellyfin |
| Isolation Levels | Read uncommitted/committed/serializable | Correct isolation behavior |
| Deadlock Handling | Simulate concurrent conflicts | Proper exception handling |
Example Test:
[Fact]
public async Task PostgreSQL_Transaction_RollbackReverts()
{
await using var context = CreateContext();
await using var transaction = await context.Database.BeginTransactionAsync();
try
{
var item = new BaseItem { Id = Guid.NewGuid(), Name = "Test" };
context.BaseItems.Add(item);
await context.SaveChangesAsync();
// Rollback
await transaction.RollbackAsync();
// Item should not exist
var retrieved = await context.BaseItems.FindAsync(item.Id);
Assert.Null(retrieved);
}
finally
{
await transaction.DisposeAsync();
}
}
5.7 Performance Tests
Objective: Ensure no significant performance regression vs .NET 9.0
Baseline Metrics (from .NET 9.0):
- Record these before migration
- Compare after migration
| Metric | .NET 9.0 Baseline | .NET 11.0 Target | Threshold |
|---|---|---|---|
| Simple Query | [X ms] | [Y ms] | <10% regression |
| Complex Query | [X ms] | [Y ms] | <10% regression |
| INSERT (single) | [X ms] | [Y ms] | <10% regression |
| INSERT (bulk 1000) | [X ms] | [Y ms] | <10% regression |
| UPDATE (single) | [X ms] | [Y ms] | <10% regression |
| Connection Pool | [X ms] | [Y ms] | Comparable |
| Memory Usage | [X MB] | [Y MB] | <15% increase |
Performance Test Tools:
- BenchmarkDotNet
- SQL query profiling (EXPLAIN ANALYZE)
- Memory profiler (dotMemory, perfview)
Example Benchmark:
[MemoryDiagnoser]
public class PostgreSQLPerformanceBenchmark
{
[Benchmark]
public async Task<int> QueryTop100Items()
{
await using var context = CreateContext();
var items = await context.BaseItems
.OrderByDescending(e => e.DateCreated)
.Take(100)
.ToListAsync();
return items.Count;
}
}
Acceptance Criteria:
- ✅ All queries: <10% slower than .NET 9.0
- ⚠️ Any query 10-20% slower: Document and investigate
- 🛑 Any query >20% slower: BLOCKING ISSUE - must resolve
5.8 Concurrency Tests
Objective: Ensure concurrent operations are safe
| Test | Scenario | Pass Criteria |
|---|---|---|
| Concurrent Reads | Multiple threads reading | No exceptions, correct data |
| Concurrent Writes | Multiple threads writing | All writes succeed, no corruption |
| Optimistic Concurrency | Concurrent update detection | Proper concurrency exceptions |
| Pessimistic Locking | Row-level locking (if used) | Locks prevent conflicts |
| Deadlock Handling | Intentional deadlock | Proper exception, rollback |
| Connection Pool Under Load | Many simultaneous connections | Pool manages correctly |
Load Test Example:
[Fact]
public async Task PostgreSQL_ConcurrentWrites_NoCorruption()
{
var tasks = Enumerable.Range(0, 50).Select(async i =>
{
await using var context = CreateContext();
var item = new BaseItem
{
Id = Guid.NewGuid(),
Name = $"Concurrent-{i}"
};
context.BaseItems.Add(item);
await context.SaveChangesAsync();
});
// Should not throw
await Task.WhenAll(tasks);
// Verify all 50 records created
await using var verifyContext = CreateContext();
var count = await verifyContext.BaseItems
.CountAsync(e => e.Name.StartsWith("Concurrent-"));
Assert.Equal(50, count);
}
5.9 Edge Case Tests
Objective: Ensure provider handles unusual scenarios
| Edge Case | Test | Expected Behavior |
|---|---|---|
| NULL Values | Insert/query NULL columns | Proper NULL handling |
| Large Result Sets | Query returning 10,000+ rows | No memory issues, streaming works |
| Long-Running Queries | Query taking >30 seconds | Timeout handling correct |
| Empty Strings | Insert "" vs NULL | Proper distinction |
| Unicode/Special Characters | Insert emoji, special chars | Proper encoding |
| Connection Drops | Simulate network failure | Proper exception, retry logic |
| Database Restart | PostgreSQL server restart | Reconnection handling |
| Out of Connections | Exhaust connection pool | Proper wait/error handling |
Step 6: Validation Checklist
Before declaring Tier 6 (PostgreSQL) complete, verify:
Connection & Configuration:
- All connection tests pass (7/7)
- Connection pooling functional
- SSL/TLS works (if applicable)
- Authentication successful
Migrations:
- All migration tests pass (5/5)
- No schema drift detected
- Can upgrade existing database
- Migration history intact
Data Operations:
- All CRUD tests pass (7/7)
- Bulk operations work
- Concurrent operations safe
Query Translation:
- All LINQ query patterns work
- Complex queries translate correctly
- Navigation loading functional
Transactions:
- All transaction tests pass (6/6)
- Rollback works correctly
- Isolation levels correct
Performance:
- Query performance <10% regression
- Connection performance comparable
- Memory usage acceptable (<15% increase)
- No memory leaks detected
Concurrency:
- All concurrency tests pass (6/6)
- No data corruption under load
- Deadlock handling works
Edge Cases:
- All edge case tests pass (8/8)
- NULL handling correct
- Large datasets handled
Manual Verification:
- Senior engineer review complete
- Database administrator sign-off
- Performance metrics documented
Step 7: Performance Baseline Comparison
Document findings:
PostgreSQL Provider Performance Report (.NET 11.0 vs .NET 9.0)
=================================================================
Query Performance:
- Simple SELECT: 2.3ms → 2.4ms (+4%)
- Complex JOIN: 15.7ms → 16.2ms (+3%)
- Aggregation: 8.1ms → 8.8ms (+9%)
Write Performance:
- Single INSERT: 1.2ms → 1.3ms (+8%)
- Bulk INSERT (1000): 156ms → 162ms (+4%)
- UPDATE: 1.5ms → 1.6ms (+7%)
Connection Pool:
- Acquire connection: 0.8ms → 0.9ms (+12%)
- Release connection: 0.3ms → 0.3ms (0%)
Memory Usage:
- Idle context: 2.1MB → 2.3MB (+10%)
- After 1000 queries: 15.3MB → 16.1MB (+5%)
Conclusion: All metrics within acceptable range (<10% regression)
Status: ✅ PASS
Contingency Plans
If Connection Issues:
- Check Npgsql 11.0.0-preview.1 release notes
- Search GitHub issues for known problems
- Test with different connection string formats
- Report bug to Npgsql team
- Consider waiting for next preview build
If Query Translation Fails:
- Identify failing LINQ patterns
- Rewrite queries using different approach
- Use raw SQL as temporary workaround
- Report to EF Core team
- Wait for provider fix (may block migration)
If Performance Degrades >20%:
- Profile with dotTrace/PerfView
- Analyze SQL execution plans (EXPLAIN)
- Optimize queries if possible
- Report performance regression to .NET team
- BLOCKING ISSUE - may need to pause migration
If Critical Bugs Discovered:
- Document bug thoroughly
- Create minimal reproduction
- Report to Npgsql/EF Core teams
- Check if workaround exists
- May need to pause migration until fix available
- Consider switching to .NET 10.0 LTS
🚨 Project 3: Jellyfin.Database.Providers.Sqlite
⚠️ CRITICAL ALTERNATIVE DATABASE PROVIDER
This is the alternative database provider for smaller Jellyfin deployments and testing.
Current State
- Framework: .NET 9.0
- Type: Class Library
- Dependencies:
- MediaBrowser.Common (Tier 5)
- Jellyfin.Database.Implementations (Tier 2)
- Jellyfin.CodeAnalysis
- Used By:
- Jellyfin.Server (Tier 11)
Package Status
| Package | Current Version | Status |
|---|---|---|
| Microsoft.EntityFrameworkCore.Sqlite | 11.0.0-preview.1 | ✅ Already upgraded (PREVIEW) |
| Microsoft.Data.Sqlite | 11.0.0-preview.1 | ✅ Already upgraded (PREVIEW) |
Migration Steps
(Similar structure to PostgreSQL provider)
Step 1: Prerequisites
- SQLite database file available for testing
- Test database with representative schema
- .NET 9.0 performance baseline
- Write permissions verified
- Database backup
Step 2: Framework Update
<TargetFramework>net11.0</TargetFramework>
Step 3: Expected Breaking Changes
Assessment: ✅ None identified
Monitor For:
- EF Core 11.0 query translation changes
- SQLite provider-specific behavior changes
- File locking behavior (critical for SQLite)
- WAL mode changes (Write-Ahead Logging)
Step 5: EXTENSIVE Testing (SQLite-Specific)
All standard tests (same as PostgreSQL) PLUS:
SQLite-Specific Tests:
Database File Operations:
- Database file created successfully
- Database file opened correctly
- File locking works correctly (critical)
- WAL mode functional (if used)
- File permissions respected
- Backup/restore operations work
- Database file size reasonable
Example File Test:
[Fact]
public async Task Sqlite_FileLocking_Works()
{
var dbPath = "test.db";
// First context
await using var context1 = CreateSqliteContext(dbPath);
await context1.Database.EnsureCreatedAsync();
// Second context on same file
await using var context2 = CreateSqliteContext(dbPath);
// Should handle locking gracefully
var item = new BaseItem { Id = Guid.NewGuid(), Name = "Test" };
context1.BaseItems.Add(item);
await context1.SaveChangesAsync(); // Should not deadlock
}
SQLite-Specific Edge Cases:
- Multiple connections to same file
- Busy timeout handling
- Disk full scenarios
- File corruption detection (if applicable)
- In-memory databases work (for testing)
🔄 Cross-Provider Integration Testing
After both providers migrate, perform comprehensive cross-provider validation:
1. Dual-Provider Tests
Objective: Ensure both providers work interchangeably
Test Scenarios:
| Test | PostgreSQL | SQLite | Pass Criteria |
|---|---|---|---|
| Configuration Switching | Use PostgreSQL config | Use SQLite config | Both work independently |
| Same Queries | Run query set | Run same query set | Both return correct results |
| Schema Migrations | Apply migrations | Apply migrations | Schema matches across providers |
| Data Integrity | Insert/query data | Insert/query data | Data consistent |
Switching Test:
[Theory]
[InlineData("PostgreSQL")]
[InlineData("SQLite")]
public async Task BothProviders_Work(string provider)
{
await using var context = CreateContext(provider);
// Should work with both providers
var item = new BaseItem { Id = Guid.NewGuid(), Name = "Test" };
context.BaseItems.Add(item);
await context.SaveChangesAsync();
var retrieved = await context.BaseItems.FindAsync(item.Id);
Assert.NotNull(retrieved);
Assert.Equal("Test", retrieved.Name);
}
2. Real-World Scenario Tests
Objective: Test actual Jellyfin use cases
Key Scenarios (run on both providers):
- User Authentication
[Fact]
public async Task UserAuthentication_Works()
{
// Create user
var user = new User { Id = Guid.NewGuid(), Username = "testuser" };
await SaveUserAsync(user);
// Authenticate
var authenticated = await AuthenticateUserAsync("testuser", "password");
Assert.True(authenticated);
}
- Media Library Queries
[Fact]
public async Task MediaLibrary_QueriesWork()
{
// Add media items
await AddMoviesAsync(100);
// Query recently added
var recent = await GetRecentlyAddedAsync(10);
Assert.Equal(10, recent.Count);
// Query by genre
var action = await GetByGenreAsync("Action");
Assert.NotEmpty(action);
}
- Playback Session Tracking
[Fact]
public async Task PlaybackTracking_Works()
{
var session = new PlaybackSession
{
UserId = testUser.Id,
ItemId = testMovie.Id,
Position = TimeSpan.FromMinutes(15)
};
await SavePlaybackSessionAsync(session);
var retrieved = await GetPlaybackPositionAsync(testUser.Id, testMovie.Id);
Assert.Equal(TimeSpan.FromMinutes(15), retrieved);
}
- User Preferences
[Fact]
public async Task UserPreferences_Persist()
{
await SetUserPreferenceAsync(testUser.Id, "Theme", "Dark");
var theme = await GetUserPreferenceAsync(testUser.Id, "Theme");
Assert.Equal("Dark", theme);
}
- Search Operations
[Fact]
public async Task Search_ReturnsResults()
{
var results = await SearchMediaAsync("Lord of the Rings");
Assert.NotEmpty(results);
Assert.All(results, r =>
Assert.Contains("lord", r.Name.ToLower()));
}
3. Load Testing
Objective: Ensure providers handle realistic load
Test Configuration:
- Concurrent users: 10-50
- Library size: 1,000-10,000 items
- Duration: 5-10 minutes
Scenarios:
| Test | Load | Duration | Pass Criteria |
|---|---|---|---|
| Concurrent Reads | 50 users querying library | 5 min | No errors, <2 sec response |
| Concurrent Writes | 10 users updating playback | 5 min | No conflicts, all writes succeed |
| Large Library | Query 10,000+ items | Per query | <5 sec response time |
| Sustained Load | 20 users, mixed operations | 10 min | Stable performance, no degradation |
Memory Under Load:
- Memory usage stable (no leaks)
- Connection pool doesn't grow indefinitely
- Database file size reasonable (SQLite)
4. Failure Recovery
Objective: Ensure graceful handling of failures
| Failure Scenario | Test | Expected Behavior |
|---|---|---|
| Connection Drop | Disconnect database mid-query | Proper exception, reconnect on next query |
| Transaction Rollback | Force transaction error | Changes reverted, database consistent |
| Database Restart | Restart PostgreSQL/SQLite | Application reconnects automatically |
| Corrupted Data | Insert invalid data | Validation exception, no corruption |
| Disk Full (SQLite) | Fill disk space | Proper error, no database corruption |
| Network Issues (PostgreSQL) | Simulate network problems | Retry logic, proper error messages |
✅ Tier 6: Completion Criteria (STRICT)
🛑 CRITICAL GATE
Do NOT proceed to Tier 7 unless ALL criteria below are met:
1. Build Success ✅
- All 3 projects build without errors
- All 3 projects build without warnings
- Code analysis passes (all projects)
- No obsolete API warnings
2. Database Connection ✅
- PostgreSQL provider connects successfully
- SQLite provider connects successfully
- Connection pooling functional (PostgreSQL)
- File locking functional (SQLite)
- Both providers handle SSL/encryption correctly
3. Migration Compatibility ✅
- All existing migrations apply successfully (both providers)
- No schema drift detected
- Migration history intact
- Can upgrade from .NET 9.0 database
- Can create new database from scratch
4. CRUD Operations ✅
- All CRUD tests pass (100%) - PostgreSQL
- All CRUD tests pass (100%) - SQLite
- Concurrency tests pass (both providers)
- Transaction tests pass (both providers)
- Bulk operations work (both providers)
5. Query Translation ✅
- LINQ queries translate correctly (PostgreSQL)
- LINQ queries translate correctly (SQLite)
- Complex queries work (joins, aggregations)
- Navigation loading functional
- Explicit/eager/lazy loading works (if used)
6. Performance Acceptable ✅
PostgreSQL:
- Query performance: <10% regression vs .NET 9.0
- Connection performance: Comparable to baseline
- Memory usage: <15% increase
- No memory leaks detected
SQLite:
- Query performance: <10% regression vs .NET 9.0
- File I/O performance: Comparable to baseline
- Memory usage: <15% increase
- Database file size reasonable
7. Integration Tests ✅
- Dual-provider tests pass (can switch between providers)
- Real-world scenario tests pass (5/5 scenarios, both providers)
- Load tests pass (within acceptable limits)
- Failure recovery tests pass (6/6 scenarios)
8. Manual Validation ✅
- Senior engineer review of provider behavior
- Database administrator verification (PostgreSQL)
- Sign-off on performance metrics
- Risk assessment complete
9. Documentation ✅
- All issues encountered documented
- Performance baselines recorded
- Known limitations documented
- Contingency plans ready
- Workarounds noted (if any)
10. Cross-Provider Validation ✅
- Same queries work on both providers
- Schema compatible across providers
- Migration scripts work on both
- Data integrity maintained
🛑 DECISION POINT
If ALL criteria met:
✅ PROCEED to Tier 7 - Database providers stable and validated
If ANY criterion fails:
⚠️ PAUSE MIGRATION - Assess severity and options:
Option A: Fix and Retry
- Document issue
- Implement fix
- Re-test failed criteria
- Retry Tier 6
Option B: Report Upstream
- File bug with Npgsql/EF Core team
- Monitor for fix
- Consider waiting for next preview
- Re-assess timeline
Option C: Implement Workaround
- If feasible workaround exists
- Document workaround in code
- Continue with documented risk
- Plan to remove workaround later
Option D: Revert Migration
- If critical blocking issue
- No workaround available
- Too risky to continue
- Switch to .NET 10.0 LTS instead
- Or stay on .NET 9.0
📊 Tier 6 Success Metrics
Final Report Template:
Tier 6 Completion Report - Database Providers
==============================================
Date: [Date]
Engineer: [Name]
Status: ✅ PASS / ⚠️ CONDITIONAL / 🛑 FAIL
PostgreSQL Provider (Npgsql 11.0.0-preview.1):
- Build: ✅ Pass
- Connection: ✅ Pass
- Migrations: ✅ Pass (32 migrations applied)
- CRUD: ✅ Pass (47/47 tests)
- Queries: ✅ Pass (89/89 tests)
- Transactions: ✅ Pass (12/12 tests)
- Performance: ✅ Pass (avg +7% regression, within threshold)
- Concurrency: ✅ Pass (25/25 tests)
- Edge Cases: ✅ Pass (18/18 tests)
SQLite Provider (EF Core Sqlite 11.0.0-preview.1):
- Build: ✅ Pass
- Connection: ✅ Pass
- Migrations: ✅ Pass (32 migrations applied)
- CRUD: ✅ Pass (47/47 tests)
- Queries: ✅ Pass (89/89 tests)
- Transactions: ✅ Pass (10/10 tests)
- Performance: ✅ Pass (avg +5% regression, within threshold)
- Concurrency: ✅ Pass (20/20 tests)
- Edge Cases: ✅ Pass (22/22 tests)
Integration Tests:
- Dual-Provider: ✅ Pass (15/15 tests)
- Real-World: ✅ Pass (10/10 scenarios)
- Load Tests: ✅ Pass (performance stable under load)
- Failure Recovery: ✅ Pass (12/12 scenarios)
Issues Encountered:
1. [None] or [List issues with resolutions]
Known Limitations:
1. [None] or [List limitations]
Performance Comparison (.NET 11.0 vs .NET 9.0):
- PostgreSQL: +7% average (range: +2% to +9%)
- SQLite: +5% average (range: +1% to +8%)
Recommendation: ✅ PROCEED TO TIER 7
Approvals:
- Senior Engineer: [Name] ✅
- DBA: [Name] ✅
- Date: [Date]
🎯 Why This Tier is So Comprehensive
- Foundation for Everything - All data access depends on this
- Preview × Preview Risk - Preview provider + preview framework
- Production Critical - Jellyfin unusable if broken
- Complex Surface Area - Connections, queries, transactions, migrations
- Performance Sensitive - Database is often the bottleneck
- Data Integrity - Cannot tolerate corruption
- No Second Chances - Must get it right before proceeding
Tier 6 is the primary risk gate for the entire migration.