# Query Execution - Quick Reference & Remediation Guide ## The Query Flow in 30 Seconds ``` Browser loads page ↓ Multiple API calls (likely different item types/filters) ↓ ItemsController.GetItems() × N ↓ Build InternalItemsQuery (with DtoOptions.EnableUserData=true by default) ↓ BaseItemRepository.GetItemsAsync() ├─ Query 1: Get filtered item IDs └─ Query 2: Load full items with eager loading: ├─ .Include(e => e.UserData) ← UserData loaded HERE ├─ .Include(e => e.Provider) ├─ .Include(e => e.Images) ├─ .Include(e => e.TvExtras) ├─ .Include(e => e.LiveTvExtras) └─ .Include(e => e.AudioExtras) ↓ DtoService.GetBaseItemDtos() - For each item: ├─ GetBaseItemDtoInternal() │ └─ AttachUserSpecificInfo() │ └─ GetUserDataDto() │ └─ GetUserData() ← Accesses in-memory UserData (NO QUERY) └─ [Potential N+1] SetItemByNameInfo() if ItemCounts requested └─ [Potential N+1] GetChildCount() if ChildCount requested ↓ Return DTOs to browser ``` ## Identified Query Issues & Solutions ### 1. Multiple API Endpoints During Page Load **Status**: ✓ **Normal behavior, not a bug** **What's Happening**: The web UI calls multiple GetItems endpoints simultaneously for: - Movies library - Series library - Recently added - Resume items - Playlists Each is a separate API call → separate database query. **Evidence to Confirm**: ```bash # Check network tab in browser DevTools # Look for multiple /Items calls with different parameters ``` **Solution**: Consolidate UI requests or add caching if same queries repeat. --- ### 2. ItemCounts Field N+1 Pattern **Status**: ⚠️ **Actual N+1 detected** **What's Happening** at [Emby.Server.Implementations/Dto/DtoService.cs](Emby.Server.Implementations/Dto/DtoService.cs#L182): ```csharp for (int index = 0; index < accessibleItems.Count; index++) { var item = accessibleItems[index]; var dto = GetBaseItemDtoInternal(item, options, user, owner); if (options.ContainsField(ItemFields.ItemCounts)) // ← If requested { SetItemByNameInfo(dto, user); // ← Called for EACH item } } ``` **Risk Level**: HIGH if ItemCounts is frequently requested **To Check**: Look for `ItemCounts` in API calls: ```bash # Browser DevTools → Network → GetItems request # Check if query parameters include ItemCounts ``` **Fix**: Batch SetItemByNameInfo operations instead of per-item: ```csharp if (options.ContainsField(ItemFields.ItemCounts)) { // Batch operation - collect all IDs first var itemIds = returnItems.Select(r => r.Id).ToList(); var itemCountsMap = GetItemCountsForItems(itemIds, user); foreach (var dto in returnItems) { if (itemCountsMap.TryGetValue(dto.Id, out var counts)) { dto.ItemCounts = counts; } } } ``` --- ### 3. ChildCount Field Potential N+1 **Status**: ⚠️ **Partially optimized** **What's Happening** at [Emby.Server.Implementations/Dto/DtoService.cs](Emby.Server.Implementations/Dto/DtoService.cs#L497): ```csharp private static int GetChildCount(Folder folder, User user) { if (folder is ICollectionFolder || folder is UserView) { return Random.Shared.Next(1, 10); // ✓ NO QUERY } return folder.GetChildCount(user); // ⚠️ POTENTIAL QUERY } ``` **Current Status**: - ✓ Collection folders & user views: return random (optimized, no query) - ⚠️ Regular folders: call GetChildCount() which MAY query **To Check**: ```bash # Enable EF logging to see if GetChildCount() queries per-item "Microsoft.EntityFrameworkCore.Database.Command": "Debug" ``` **Potential Fix**: Cache folder child counts: ```csharp private static readonly Dictionary _childCountCache = new(); private static int GetChildCount(Folder folder, User user) { if (folder is ICollectionFolder || folder is UserView) { return Random.Shared.Next(1, 10); } if (_childCountCache.TryGetValue(folder.Id, out var count)) { return count; } count = folder.GetChildCount(user); _childCountCache[folder.Id] = count; return count; } ``` --- ### 4. People Field Processing **Status**: ⚠️ **Per-item operation** **What's Happening** at [Emby.Server.Implementations/Dto/DtoService.cs](Emby.Server.Implementations/Dto/DtoService.cs#L241): ```csharp if (options.ContainsField(ItemFields.People)) { AttachPeople(dto, item, user); // ← Called for EACH item } ``` **Risk Level**: MEDIUM - depends on AttachPeople implementation **To Investigate**: - Search for AttachPeople implementation - Check if it queries database per-item --- ### 5. MediaSources Retrieval **Status**: ⚠️ **Per-item operation** **What's Happening** at [Emby.Server.Implementations/Dto/DtoService.cs](Emby.Server.Implementations/Dto/DtoService.cs#L264): ```csharp if (item is IHasMediaSources && options.ContainsField(ItemFields.MediaSources)) { dto.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, true, user).ToArray(); } ``` **Risk Level**: MEDIUM - depends on GetStaticMediaSources implementation **To Investigate**: - Check if GetStaticMediaSources queries database - If it does, batch retrieve all media sources --- ## How to Identify Which N+1 Pattern Is Occurring ### Step 1: Enable SQL Logging Modify [Jellyfin.Server/Resources/Configuration/logging.json](Jellyfin.Server/Resources/Configuration/logging.json): ```json { "Serilog": { "MinimumLevel": { "Override": { "Microsoft.EntityFrameworkCore.Database.Command": "Debug" } } } } ``` ### Step 2: Clear Logs ```bash rm /var/log/jellyfin/log_*.log # or for development: clear logs folder ``` ### Step 3: Load Web UI Page Navigate to the slow page and note the time. ### Step 4: Analyze Logs ```bash # Look for timestamp of the page load grep "SELECT" /var/log/jellyfin/log_*.log | tail -50 # Look for patterns like: # - Same query repeated multiple times # - "SELECT COUNT(*)" followed by "SELECT * FROM [BaseItems]" # - Queries with same WHERE clause but different parameters ``` ### Step 5: Correlate with Fields Requested Check browser DevTools for API parameters: ```javascript // In browser console fetch("/Items?userId=...&fields=ItemCounts,ChildCount&...").then(r => r.json()).then(console.log) ``` --- ## Performance Impact Summary | Issue | Queries per Page Load | User Impact | Fix Priority | |-------|---------------------|------------|--------------| | Multiple API endpoints | 2-5 | Slow page load | Medium | | ItemCounts N+1 | +20-100 | Very slow for large libraries | High | | ChildCount N+1 | +10-50 | Slow for folder listings | Medium | | People field | +5-20 | Slow if many items | Low | | MediaSources | +5-20 | Slow if many video items | Low | --- ## Recommended Testing ### Test Case 1: Verify UserData is Not N+1 ``` ✓ Enable EF logging ✓ Load library page with 50 items ✓ Search logs for "SELECT.*FROM.*UserData" ✓ Should see only 1-2 queries for UserData (not 50) ``` ### Test Case 2: Check ItemCounts Impact ``` ✓ Note count of queries in normal page load ✓ Remove ItemCounts from request ✓ Reload and compare query count ✓ Difference = cost of ItemCounts feature ``` ### Test Case 3: Monitor API Calls ``` ✓ Open DevTools Network tab ✓ Clear filters ✓ Load home page ✓ Observe and count /Items calls ✓ Note different query parameters ``` --- ## Action Items Priority ### 🔴 HIGH Priority - [ ] Investigate if ItemCounts field causes N+1 queries - [ ] Batch SetItemByNameInfo operations - [ ] Profile library page load with 1000+ items ### 🟡 MEDIUM Priority - [ ] Investigate ChildCount field N+1 potential - [ ] Add caching to GetChildCount - [ ] Verify AttachPeople doesn't query per-item ### 🟢 LOW Priority - [ ] Investigate MediaSources retrieval - [ ] Consider batching other per-item operations - [ ] Monitor performance over time --- ## References - Main Query Execution: [QUERY_EXECUTION_ANALYSIS.md](QUERY_EXECUTION_ANALYSIS.md) - ItemsController: [Jellyfin.Api/Controllers/ItemsController.cs](Jellyfin.Api/Controllers/ItemsController.cs#L166) - DtoService: [Emby.Server.Implementations/Dto/DtoService.cs](Emby.Server.Implementations/Dto/DtoService.cs#L160) - BaseItemRepository: [Jellyfin.Server.Implementations/Item/BaseItemRepository.cs](Jellyfin.Server.Implementations/Item/BaseItemRepository.cs#L421)