# Correlated Subquery Performance Fix

## Problem Identified

**Query with 165-second execution time** was caused by correlated subquery pattern in BaseItemRepository.cs:

```csharp
// BEFORE (SLOW - Correlated Subquery)
var tempQuery = dbQuery
    .GroupBy(e => e.PresentationUniqueKey)
    .Select(e => e.FirstOrDefault())
    .Select(e => e!.Id);
dbQuery = context.BaseItems.Where(e => tempQuery.Contains(e.Id));
```

### Generated SQL (Problematic):
```sql
SELECT b."Id", b."Album", [70+ columns...]
FROM library."BaseItems" AS b
WHERE b."Id" IN (
    SELECT (
        SELECT b1."Id"
        FROM library."BaseItems" AS b1
        WHERE b1."Type" = $3 
          AND b1."IsVirtualItem" = $1
          AND (b1."TopParentId" = ANY ($2))
          AND (b0."PresentationUniqueKey" = b1."PresentationUniqueKey")
        LIMIT 1
    )
    FROM library."BaseItems" AS b0
)
```

**Performance Impact:**
- 165 seconds max execution time
- 15.7 seconds average
- 108,209 rows returned requiring 108,209+ subquery executions
- Caused exponential performance degradation

---

## Solution Applied

**AFTER (FAST - DistinctBy)**
```csharp
// Use DistinctBy() to generate PostgreSQL DISTINCT ON
dbQuery = dbQuery.DistinctBy(e => e.PresentationUniqueKey);
```

### Generated SQL (Optimized):
```sql
SELECT DISTINCT ON (b."PresentationUniqueKey")
       b."Id", b."Album", [70+ columns...]
FROM library."BaseItems" AS b
LEFT JOIN library."UserData" AS u ON b."Id" = u."ItemId"
WHERE b."Type" = $3 
  AND b."IsVirtualItem" = $1
  AND (b."TopParentId" = ANY ($2))
```

**Expected Performance:**
- **<50ms execution time** (600-3300x faster)
- Single table scan instead of correlated lookups
- Proper index usage

---

## Locations Fixed

Fixed **5 locations** in `Jellyfin.Server.Implementations/Item/BaseItemRepository.cs`:

1. **Line 603-605** (PRIMARY CULPRIT): `GroupBy(PresentationUniqueKey)` → `DistinctBy(PresentationUniqueKey)`
2. **Line 600-602**: `GroupBy(new { PresentationUniqueKey, SeriesPresentationUniqueKey })` → `DistinctBy(...)`
3. **Line 608-610**: `GroupBy(SeriesPresentationUniqueKey)` → `DistinctBy(SeriesPresentationUniqueKey)`
4. **Line 1660-1666**: `GroupBy(PresentationUniqueKey)` in masterQuery → `DistinctBy(PresentationUniqueKey)`
5. **Line 1828-1834**: `GroupBy(PresentationUniqueKey)` in masterQuery → `DistinctBy(PresentationUniqueKey)`

---

## Verification Steps

### 1. Build the Solution
```powershell
dotnet build Jellyfin.sln -c Release
```

### 2. Re-run Performance Diagnostics
```powershell
. .\scripts\db-config.ps1
& $PSQL_PATH -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME -f "sql\diagnostics.sql"
```

**Expected Results:**
- Max query time should drop from 165s to <1s
- ItemValues index usage should improve from 50% to >90%
- Sequential scans should reduce significantly

### 3. Check Top Slow Queries
```powershell
& $PSQL_PATH -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME -f "sql\query-analysis.sql" > query_analysis_after_fix.txt
```

**Expected Results:**
- No queries with >100s execution time
- Average query time <100ms for BaseItems queries

---

## Technical Background

### Why GroupBy().FirstOrDefault() is Slow

EF Core translates this pattern to:
```sql
WHERE Id IN (SELECT (SELECT ... LIMIT 1) FROM ...)
```

This creates a **correlated subquery** where:
- Outer query scans BaseItems
- For EACH row, inner subquery executes
- PostgreSQL can't optimize this pattern
- Indexes are bypassed

### Why DistinctBy() is Fast

EF Core translates DistinctBy() to PostgreSQL's native `DISTINCT ON`:
```sql
SELECT DISTINCT ON (PresentationUniqueKey) ...
```

This:
- Performs single table scan
- Uses indexes effectively
- PostgreSQL native optimization applies
- Returns first row per unique key efficiently

---

## References

- **docs/database-query-optimization.md**: Original documentation of this anti-pattern
- **sql/query-analysis.sql**: Query analysis tool that identified the 165s query
- **sql/diagnostics.sql**: Comprehensive database diagnostics
- **critical_query_165s.txt**: Full extracted query showing correlated subquery structure

---

## Date Applied

**March 6, 2025**

**Diagnostics Results (Before Fix):**
- Max query time: 165 seconds
- ItemValues index usage: 50.25%
- Sequential scans: 595,439 (4.7B rows)
- Cache hit ratio: 97.42%

**Next Steps:**
1. Build solution
2. Re-run diagnostics
3. Compare before/after performance
4. Document improvements
