# Memory Optimization Recommendations for Allstarr ## Current Implementation Status ✅ **COMPLETED**: Mark-for-deletion system with 24-hour delay ✅ **COMPLETED**: Persistent favorites tracking using JSON files ✅ **COMPLETED**: Cache-first copying for favorites (avoids re-downloads) ✅ **COMPLETED**: Dependency injection for CacheCleanupService to process pending deletions ## Memory Optimization Strategies ### 1. Collection Optimizations **Current Issues:** - Multiple `List`, `List`, `List` collections created during searches - Large `Dictionary` objects for Jellyfin metadata - Concurrent collections like `ConcurrentDictionary` for sessions **Recommendations:** ```csharp // Use ArrayPool for temporary collections private static readonly ArrayPool SongArrayPool = ArrayPool.Shared; // Use Span for temporary operations ReadOnlySpan ProcessSongs(ReadOnlySpan songs) { ... } // Use IAsyncEnumerable for streaming large results IAsyncEnumerable SearchSongsStreamAsync(string query); ``` ### 2. JSON Serialization Optimizations **Current Issues:** - Heavy use of `JsonSerializer.Deserialize>()` - Multiple serialization/deserialization cycles for caching **Recommendations:** ```csharp // Use System.Text.Json source generators for better performance [JsonSerializable(typeof(List))] [JsonSerializable(typeof(Dictionary))] public partial class AllstarrJsonContext : JsonSerializerContext { } // Use JsonDocument for read-only scenarios instead of Dictionary JsonDocument.Parse(json).RootElement.GetProperty("Items") ``` ### 3. Caching Strategy Improvements **Current Issues:** - File-based caching creates multiple copies of data - Redis and file caches can contain duplicate data **Recommendations:** ```csharp // Implement cache eviction policies public class LRUCache where TKey : notnull { private readonly int _maxSize; private readonly Dictionary> _cache; private readonly LinkedList _lruList; } // Use weak references for large objects private readonly WeakReference> _cachedSongs = new(null); ``` ### 4. String Interning and Optimization **Current Issues:** - Many duplicate strings (artist names, album titles) across collections - Path strings created repeatedly **Recommendations:** ```csharp // Use string interning for common values private static readonly ConcurrentDictionary InternedStrings = new(); public static string Intern(string value) => InternedStrings.GetOrAdd(value, v => v); // Use StringBuilder for path construction private static readonly ThreadLocal PathBuilder = new(() => new StringBuilder(256)); ``` ### 5. Background Service Optimizations **Current Issues:** - Multiple background services running simultaneously - Potential memory leaks in long-running services **Recommendations:** ```csharp // Implement proper disposal patterns public class CacheCleanupService : BackgroundService, IDisposable { private readonly SemaphoreSlim _semaphore = new(1, 1); protected override async Task ExecuteAsync(CancellationToken stoppingToken) { using var _ = await _semaphore.WaitAsync(stoppingToken); // ... cleanup logic } public override void Dispose() { _semaphore?.Dispose(); base.Dispose(); } } ``` ### 6. HTTP Client Optimizations **Current Issues:** - Multiple HTTP clients for different services - Large response buffers **Recommendations:** ```csharp // Use HttpClientFactory with proper configuration builder.Services.AddHttpClient(client => { client.DefaultRequestHeaders.Add("User-Agent", "Allstarr/1.0"); client.Timeout = TimeSpan.FromSeconds(30); }) .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { MaxConnectionsPerServer = 10, PooledConnectionLifetime = TimeSpan.FromMinutes(15) }); // Stream large responses instead of loading into memory using var stream = await response.Content.ReadAsStreamAsync(); using var reader = new StreamReader(stream); ``` ### 7. Container Memory Limits **Docker Configuration:** ```dockerfile # Set memory limits in docker-compose.yml services: allstarr: deploy: resources: limits: memory: 512M reservations: memory: 256M ``` **Runtime Configuration:** ```csharp // Configure GC for container environments GCSettings.LatencyMode = GCLatencyMode.Batch; GC.Collect(2, GCCollectionMode.Optimized); ``` ## Immediate Actions (Priority Order) 1. **Enable GC monitoring** - Add memory usage logging to identify hotspots 2. **Implement cache size limits** - Prevent unbounded growth of in-memory caches 3. **Use object pooling** - For frequently allocated objects like Song/Album/Artist 4. **Stream large responses** - Instead of loading entire JSON responses into memory 5. **Optimize JSON serialization** - Use source generators and reduce Dictionary usage ## Monitoring Recommendations ```csharp // Add memory monitoring to Program.cs builder.Services.AddHostedService(); public class MemoryMonitoringService : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { var memoryUsage = GC.GetTotalMemory(false); var gen0 = GC.CollectionCount(0); var gen1 = GC.CollectionCount(1); var gen2 = GC.CollectionCount(2); _logger.LogInformation("Memory: {Memory:N0} bytes, GC: Gen0={Gen0}, Gen1={Gen1}, Gen2={Gen2}", memoryUsage, gen0, gen1, gen2); await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken); } } } ```