diff --git a/MEMORY_OPTIMIZATION_RECOMMENDATIONS.md b/MEMORY_OPTIMIZATION_RECOMMENDATIONS.md deleted file mode 100644 index a9a3b27..0000000 --- a/MEMORY_OPTIMIZATION_RECOMMENDATIONS.md +++ /dev/null @@ -1,191 +0,0 @@ -# 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); - } - } -} -``` \ No newline at end of file