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 PURGEveya Redis restart. Ratio <1.0 ise swap kullanılıyor — çok kötü!