Ana içeriğe geç

In-Memory Cache

In-Memory cache, uygulama belleğinde veri saklayarak en hızlı erişimi sağlar; yanlış kullanımlar bellek şişmesine ve tutarsızlığa yol açar.


1. IMemoryCache Yerine Static Dictionary Kullanmak

Yanlış Kullanım: Static dictionary ile manuel cache yapmak.

public static class ProductCache
{
    private static readonly Dictionary<int, Product> _cache = new();

    public static Product Get(int id) => _cache.GetValueOrDefault(id);
    public static void Set(int id, Product product) => _cache[id] = product;
    // Thread-safe değil, TTL yok, bellek sınırı yok
}

İdeal Kullanım: IMemoryCache ile yönetilen cache kullanın.

builder.Services.AddMemoryCache(options =>
{
    options.SizeLimit = 1000;
});

public class ProductService
{
    private readonly IMemoryCache _cache;

    public async Task<Product> GetAsync(int id)
    {
        return await _cache.GetOrCreateAsync($"product:{id}", async entry =>
        {
            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
            entry.SlidingExpiration = TimeSpan.FromMinutes(2);
            entry.Size = 1;
            return await _repository.GetByIdAsync(id);
        });
    }
}

2. Cache Size Limiti Koymamak

Yanlış Kullanım: Sınırsız cache ile bellek tükenmesi riski.

builder.Services.AddMemoryCache(); // Size limit yok

// Her istek yeni cache entry ekler, bellek sürekli büyür
_cache.Set($"user-session:{sessionId}", sessionData);

İdeal Kullanım: Size limit ve eviction policy belirleyin.

builder.Services.AddMemoryCache(options =>
{
    options.SizeLimit = 2048; // Maksimum entry sayısı
    options.CompactionPercentage = 0.25; // %75 dolduğunda %25 temizle
});

_cache.Set($"user-session:{sessionId}", sessionData, new MemoryCacheEntryOptions
{
    Size = 1,
    Priority = CacheItemPriority.Normal,
    AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30)
});

3. Lazy Loading ile Cache Miss Yönetimi

Yanlış Kullanım: Her cache miss’te senkron veritabanı çağrısı.

public Product GetProduct(int id)
{
    if (_cache.TryGetValue(id, out Product product))
        return product;

    product = _repository.GetById(id); // Senkron çağrı, thread bloklanır
    _cache.Set(id, product);
    return product;
}

İdeal Kullanım: GetOrCreateAsync ile asenkron lazy loading yapın.

public async Task<Product> GetProductAsync(int id)
{
    return await _cache.GetOrCreateAsync($"product:{id}", async entry =>
    {
        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
        entry.RegisterPostEvictionCallback((key, value, reason, state) =>
        {
            _logger.LogInformation("Cache evicted: {Key}, Reason: {Reason}", key, reason);
        });
        return await _repository.GetByIdAsync(id);
    });
}

4. Multi-Server Ortamda In-Memory Cache

Yanlış Kullanım: Birden fazla sunucuda senkronize olmayan in-memory cache.

// Server 1: Ürün fiyatını günceller, kendi cache'ini temizler
await _cache.RemoveAsync("product:1");

// Server 2: Hala eski fiyatı gösterir, kendi cache'inden okur
var product = _cache.Get("product:1"); // Eski veri

İdeal Kullanım: Multi-server ortamda IDistributedCache kullanın.

// Development: In-Memory
if (builder.Environment.IsDevelopment())
{
    builder.Services.AddDistributedMemoryCache();
}
else
{
    // Production: Redis
    builder.Services.AddStackExchangeRedisCache(options =>
    {
        options.Configuration = builder.Configuration.GetConnectionString("Redis");
        options.InstanceName = "MyApp:";
    });
}

5. Cache-Aside Pattern Kullanmamak

Yanlış Kullanım: Cache ve veritabanı senkronizasyonunu her yerde tekrarlamak.

// Her serviste aynı pattern tekrarlanıyor
var cached = await _cache.GetAsync(key);
if (cached == null)
{
    var data = await _repository.GetAsync(id);
    await _cache.SetAsync(key, data, expiry);
    return data;
}
return cached;

İdeal Kullanım: Generic cache-aside helper ile tekrarı ortadan kaldırın.

public class CacheService : ICacheService
{
    private readonly IMemoryCache _cache;

    public async Task<T> GetOrCreateAsync<T>(
        string key,
        Func<Task<T>> factory,
        TimeSpan? expiry = null)
    {
        if (_cache.TryGetValue(key, out T cached))
            return cached;

        var value = await factory();

        var options = new MemoryCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = expiry ?? TimeSpan.FromMinutes(10)
        };

        _cache.Set(key, value, options);
        return value;
    }

    public void Remove(string key) => _cache.Remove(key);
}

// Kullanım
var product = await _cacheService.GetOrCreateAsync(
    $"product:{id}",
    () => _repository.GetByIdAsync(id),
    TimeSpan.FromMinutes(15));