RRedis Handbook

UZMAN

Anti-Patterns & Troubleshooting

Redis 8 ve .NET 10 ile caching, veri yapıları, messaging, ölçekleme, dayanıklılık ve production operasyonları.

Kod örneği görünümü Bu sayfadaki eşleşen örnekleri seçilen istemciye göre gösterir.
Anti-Pattern Sorun Doğru Yaklaşım
KEYS komutu O(N) — production'ı bloklar SCAN ile iterate et
Big key (>1MB) >85KB → .NET LOH (Gen2 GC pressure), >1MB → network buffer spike + Redis single-thread blokaj, DELETE/UNLINK bile yavaş Parçala (Hash field'ları, chunk'lar), UNLINK (async), MEMORY USAGE key ile kontrol
Hot key Tek shard'a aşırı yük Local in-memory cache + random suffix
TTL yok Memory leak — eviction'a düşer Her key'e explicit TTL
Fire-and-forget everywhere Hata yakalanamaz Critical ops'larda await kullan
ConnectionMultiplexer per-request Connection explosion Singleton kullan (GetDatabase(n) ile multi-DB bile olsa Singleton yeterli)
Blocking ops (BLPOP) uzun timeout Connection havuzunu tüketir Ayrı multiplexer veya kısa timeout
Multi-DB + Cluster/Sentinel Cluster'da SELECT hata verir, Sentinel sadece db0 monitor eder Key prefix ile namespace (cache:, session:)
Her servis ayrı Redis instance Kaynak israfı, yönetim karmaşıklığı Tek instance + ACL + key prefix

Hot Key Çözümü

# Sorun: "product:popular" key'ine saniyede 50K istek
# Çözüm: Local cache + Redis read replicas

# Monitoring:
redis-cli --hotkeys
redis-cli OBJECT FREQ product:popular
// IMemoryCache + Redis hybrid (hot key koruması)
public class HybridCacheService
{
    private readonly IDatabase _redis;
    private readonly IMemoryCache _localCache;
    private readonly TimeSpan _localTtl = TimeSpan.FromSeconds(5);

    public HybridCacheService(IConnectionMultiplexer mux, IMemoryCache memoryCache)
    {
        _redis = mux.GetDatabase();
        _localCache = memoryCache;
    }

    public async Task<T?> GetHotKeyAsync<T>(string key) where T : class
    {
        // 1. Local cache (L1)
        if (_localCache.TryGetValue(key, out T? local))
            return local;

        // 2. Redis (L2)
        var cached = await _redis.StringGetAsync(key);
        if (!cached.HasValue) return null;

        var value = JsonSerializer.Deserialize<T>(cached!);
        _localCache.Set(key, value, _localTtl); // L1'e yaz
        return value;
    }
}

Memory Leak Teşhisi

# 1. Genel memory durumu
INFO memory
# used_memory_human: 1.2GB
# mem_fragmentation_ratio: 1.15  (>1.5 = sorun!)
# maxmemory: 2GB

# 2. Büyük key'leri bul (sampling-based)
redis-cli --bigkeys
# [00.00%] Biggest string: "cache:report:annual" (4.2MB)

# 3. Belirli key'in memory maliyeti
MEMORY USAGE cache:report:annual
# 4423680 bytes

# 4. Idle key'leri bul (TTL olmayan zombiler)
# redis.conf: maxmemory-policy = allkeys-lfu gerekir (OBJECT FREQ için)
OBJECT IDLETIME cache:stale:key    # son erişimden beri saniye
OBJECT FREQ cache:hot:key          # erişim frekansı (LFU)

# 5. Sistematik leak arama: SCAN + TTL kontrolü
# TTL'siz key'leri bul (potansiyel leak):
redis-cli --scan --pattern "*" | while read key; do
  ttl=$(redis-cli TTL "$key")
  if [ "$ttl" = "-1" ]; then
    size=$(redis-cli MEMORY USAGE "$key")
    echo "$key: $size bytes (NO TTL)"
  fi
done | sort -t: -k2 -rn | head -20

# 6. Memory doctor
MEMORY DOCTOR
# "Sam, I have a few observations..."
public class MemoryDiagnosticsService
{
    private readonly IConnectionMultiplexer _mux;
    private readonly ILogger<MemoryDiagnosticsService> _logger;

    public MemoryDiagnosticsService(IConnectionMultiplexer mux,
        ILogger<MemoryDiagnosticsService> logger)
    {
        _mux = mux;
        _logger = logger;
    }

    // TTL'siz büyük key'leri bul
    public async Task<List<KeyMemoryInfo>> FindLeakyKeysAsync(
        string pattern = "*", long minBytes = 10_000)
    {
        var server = _mux.GetServers().First();
        var db = _mux.GetDatabase();
        var leaks = new List<KeyMemoryInfo>();

        await foreach (var key in server.KeysAsync(pattern: pattern, pageSize: 500))
        {
            var ttl = await db.KeyTimeToLiveAsync(key);
            if (ttl is not null) continue; // TTL var → muhtemelen OK

            var memoryUsage = await db.ExecuteAsync("MEMORY", "USAGE", key.ToString());
            var bytes = (long?)memoryUsage ?? 0;

            if (bytes >= minBytes)
            {
                leaks.Add(new KeyMemoryInfo
                {
                    Key = key.ToString(),
                    Bytes = bytes,
                    Type = (await db.KeyTypeAsync(key)).ToString()
                });
            }
        }

        return leaks.OrderByDescending(k => k.Bytes).Take(50).ToList();
    }

    // Scheduled: haftalık memory raporu
    public async Task LogMemoryReportAsync()
    {
        var server = _mux.GetServers().First();
        var info = await server.InfoAsync("memory");
        var mem = info.First().ToDictionary(p => p.Key, p => p.Value);

        _logger.LogInformation(
            "Redis Memory: used={Used}, peak={Peak}, frag={Frag}, maxmemory={Max}",
            mem["used_memory_human"],
            mem["used_memory_peak_human"],
            mem["mem_fragmentation_ratio"],
            mem.GetValueOrDefault("maxmemory_human") ?? "unlimited");
    }
}

public record KeyMemoryInfo
{
    public string Key { get; init; } = "";
    public long Bytes { get; init; }
    public string Type { get; init; } = "";
}

Fragmentation ratio >1.5: Redis OS'tan daha fazla memory almış ama etkin kullanamıyor. Çözüm: MEMORY PURGE veya Redis restart. Ratio <1.0 ise swap kullanılıyor — çok kötü!