Files
pgsql-jellyfin/.github/upgrades/scenarios/new-dotnet-version_02bc64/Risk Management Strategies.md
T

43 KiB
Raw Blame History

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:

  1. Preventive Controls - Stop problems before they occur (bottom-up strategy, tier ordering)
  2. Detective Controls - Identify problems early (extensive testing at each tier)
  3. 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:

  1. 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
  2. Progressive Exposure

    • Each tier tests more functionality
    • Build confidence incrementally
    • Learn .NET 11.0 behavior patterns early
  3. Pause Points

    • After Tier 2: Assess preview framework stability
    • After Tier 6: Database providers validated (critical milestone)
    • After Tier 11: Application functional
  4. Rollback Capability

    • Git branch isolation (upgrade-to-NET11)
    • Can revert entire migration with single git operation
    • Alternative target ready (.NET 10.0 LTS)
  5. 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:

  1. Dedicated Tier (Tier 6)

    • Isolated focus on database providers only
    • No mixing with other concerns
    • Full attention on validation
  2. 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)
  1. Dual Provider Validation

    • Test both PostgreSQL AND SQLite
    • Ensure both work independently
    • Validate switching between providers
    • Real-world scenario testing
  2. Performance Baseline

    • Document .NET 9.0 query performance
    • Compare .NET 11.0 against baseline
    • Acceptable: <10% regression
    • Red flag: >20% regression (investigate or pause)
  3. 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:

  1. Native Library Verification

    • Test library loading explicitly
    • Verify cross-platform compatibility (Linux primary)
    • Validate font rendering (HarfBuzz)
  2. Functional Testing

    • Image processing workflows
    • Thumbnail generation
    • Format conversions
  3. 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:

  1. Foundation First

    • Tiers 2-10 stable before reaching application
    • Issues isolated to lower tiers
    • Less debugging needed at application level
  2. Comprehensive Smoke Testing

✓ Application starts
   ✓ Web UI loads
   ✓ Authentication works
   ✓ Library browsing
   ✓ Playback initiates
   ✓ API responds
  1. Staged Validation
    • Build validation first
    • Startup validation next
    • Feature validation last

🔄 The Bottom-Up Strategy as Risk Management

Why Bottom-Up Reduces Risk

  1. 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)
  1. 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)
  1. 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
  2. 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:

  1. Tier 2-4 critical failures (preview framework issues)
  2. Tier 6 database provider failure (GO/NO-GO gate)
  3. >20% test failures in any tier
  4. >30% performance degradation
  5. Blocking preview bugs with no workaround
  6. 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:

  1. Structural: Bottom-up architecture naturally limits blast radius
  2. Procedural: Strict tier completion criteria prevent advancement with issues
  3. Technical: Comprehensive testing catches problems early
  4. Organizational: Clear decision points and rollback triggers
  5. 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

  1. Emby.Naming - File/folder naming logic (Low Risk)
  2. Jellyfin.Database.Providers.Postgres - PostgreSQL EF Core provider ⚠️ (CRITICAL)
  3. 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

  1. Update Project File
<TargetFramework>net11.0</TargetFramework>
  1. Build and Validate

    • Build succeeds without errors/warnings
    • Naming pattern logic compiles
    • File/folder naming utilities unchanged
  2. 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:

  1. Existing Migrations Valid

    • All existing migrations compile
    • Migration files unchanged
    • No breaking changes in migration code
  2. Apply to Empty Database

    • Can create database from scratch
    • All migrations apply in order
    • Final schema matches expected
dotnet ef database update --connection "..." 
  1. Upgrade from Previous Version

    • Can upgrade existing .NET 9.0 database
    • Data preserved during upgrade
    • No data loss or corruption
  2. Migration History Table

    • __EFMigrationsHistory table accessible
    • History records correct
    • Can query migration status
  3. 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):

  1. Simple Queries

    • Basic WHERE clauses
    • ORDER BY clauses
    • TOP/LIMIT clauses
  2. Complex Queries

    • JOINs (inner, left, right)
    • GROUP BY with aggregations
    • Subqueries
    • DISTINCT operations
  3. Navigation Properties

    • Eager loading (.Include())
    • Explicit loading (.Load())
    • Lazy loading (if enabled)
    • Select projection with nested objects
  4. 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:

  1. Check Npgsql 11.0.0-preview.1 release notes
  2. Search GitHub issues for known problems
  3. Test with different connection string formats
  4. Report bug to Npgsql team
  5. Consider waiting for next preview build

If Query Translation Fails:

  1. Identify failing LINQ patterns
  2. Rewrite queries using different approach
  3. Use raw SQL as temporary workaround
  4. Report to EF Core team
  5. Wait for provider fix (may block migration)

If Performance Degrades >20%:

  1. Profile with dotTrace/PerfView
  2. Analyze SQL execution plans (EXPLAIN)
  3. Optimize queries if possible
  4. Report performance regression to .NET team
  5. BLOCKING ISSUE - may need to pause migration

If Critical Bugs Discovered:

  1. Document bug thoroughly
  2. Create minimal reproduction
  3. Report to Npgsql/EF Core teams
  4. Check if workaround exists
  5. May need to pause migration until fix available
  6. 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):

  1. 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);
}
  1. 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);
   }
  1. 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);
   }
  1. 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);
   }
  1. 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

  1. Foundation for Everything - All data access depends on this
  2. Preview × Preview Risk - Preview provider + preview framework
  3. Production Critical - Jellyfin unusable if broken
  4. Complex Surface Area - Connections, queries, transactions, migrations
  5. Performance Sensitive - Database is often the bottleneck
  6. Data Integrity - Cannot tolerate corruption
  7. No Second Chances - Must get it right before proceeding

Tier 6 is the primary risk gate for the entire migration.