RRedis Handbook

ORTA

Key Expiration & Eviction Stratejileri

Key expire olunca tetiklenen event — cleanup logic, audit trail, veya cascade invalidation için kullanılır.

Kod örneği görünümü Bu sayfadaki eşleşen örnekleri seçilen istemciye göre gösterir.

TTL Yönetimi

# TTL ata
SET key "value" EX 3600         # saniye
SET key "value" PX 3600000      # milisaniye
EXPIRE key 3600                 # mevcut key'e TTL ekle
PEXPIRE key 3600000
EXPIREAT key 1748390400         # Unix timestamp

# TTL sorgula
TTL key                         # saniye (-1: TTL yok, -2: key yok)
PTTL key                        # milisaniye

# TTL kaldır
PERSIST key

# Atomik increment + expire (Lua ile — native INCREX komutu yoktur)
EVAL "local v = redis.call('INCR', KEYS[1]); redis.call('EXPIRE', KEYS[1], ARGV[1]); return v" 1 mycounter 60
public class ExpirationService
{
    private readonly IDatabase _redis;

    public ExpirationService(IConnectionMultiplexer mux)
        => _redis = mux.GetDatabase();

    // Set + TTL (en yaygın)
    public async Task SetWithExpiryAsync(string key, string value, TimeSpan ttl)
    {
        await _redis.StringSetAsync(key, value, ttl);
    }

    // Mevcut key'e TTL ekle/güncelle
    public async Task SetExpiryAsync(string key, TimeSpan ttl)
    {
        await _redis.KeyExpireAsync(key, ttl);
    }

    // Belirli bir zaman (absolute expiration)
    public async Task SetExpiryAtAsync(string key, DateTime expireAt)
    {
        await _redis.KeyExpireAsync(key, expireAt);
    }

    // Kalan TTL kontrol
    public async Task<TimeSpan?> GetTtlAsync(string key)
    {
        return await _redis.KeyTimeToLiveAsync(key);
    }

    // Sliding expiration pattern
    public async Task<string?> GetWithSlidingExpiryAsync(string key, TimeSpan slidingTtl)
    {
        var value = await _redis.StringGetAsync(key);
        if (value.HasValue)
        {
            // Her okumada TTL yenile
            await _redis.KeyExpireAsync(key, slidingTtl);
        }
        return value;
    }

    // Keyspace notifications ile expire dinle
    public async Task SubscribeToExpirationsAsync(Action<string> onExpired)
    {
        // redis.conf: notify-keyspace-events Ex
        var sub = _redis.Multiplexer.GetSubscriber();
        await sub.SubscribeAsync(
            RedisChannel.Pattern("__keyevent@0__:expired"),
            (channel, key) => onExpired(key.ToString()));
    }

    // Atomik increment + expire (Lua script)
    private static readonly LuaScript _incrExScript = LuaScript.Prepare(@"
        local v = redis.call('INCR', @key)
        if v == 1 then
            redis.call('EXPIRE', @key, @ttl)
        end
        return v
    ");

    public async Task<long> IncrementWithExpiryAsync(string key, int ttlSeconds = 60)
    {
        var result = await _redis.ScriptEvaluateAsync(_incrExScript, new
        {
            key = (RedisKey)key,
            ttl = ttlSeconds
        });
        return (long)result;
    }
}

Eviction Policies

Policy Davranış Ne Zaman
noeviction Yeni yazım hata verir Veri kaybı kabul edilemez
allkeys-lru En az kullanılanı sil Genel cache
allkeys-lfu En az frekansla kullanılanı sil Hot data koruması
volatile-lru TTL'li key'lerden LRU TTL'siz key'ler korunmalı
volatile-lfu TTL'li key'lerden LFU TTL'siz key'ler korunmalı
volatile-ttl En kısa TTL'yi sil Doğal sıralama
allkeys-random Rastgele sil Uniform erişim

Önerilen: Cache → allkeys-lru. Persistent store → noeviction. Hot data koruması → allkeys-lfu.

Keyspace Notifications (Expired Event)

# redis.conf'ta etkinleştir (varsayılan kapalı):
notify-keyspace-events Ex
# E = keyevent, x = expired events
# Runtime'da:
CONFIG SET notify-keyspace-events Ex
// .NET'te expired key event'lerini dinle
public class KeyExpirationListener : BackgroundService
{
    private readonly IConnectionMultiplexer _mux;
    private readonly ILogger<KeyExpirationListener> _logger;
    private readonly IServiceScopeFactory _scopeFactory;

    public KeyExpirationListener(IConnectionMultiplexer mux,
        ILogger<KeyExpirationListener> logger, IServiceScopeFactory scopeFactory)
    {
        _mux = mux;
        _logger = logger;
        _scopeFactory = scopeFactory;
    }

    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        var sub = _mux.GetSubscriber();

        // db0'daki tüm expire event'lerini dinle
        await sub.SubscribeAsync(
            RedisChannel.Pattern("__keyevent@0__:expired"),
            async (channel, expiredKey) =>
            {
                var key = expiredKey.ToString();
                _logger.LogDebug("Key expired: {Key}", key);

                // Prefix'e göre aksiyon al
                if (key.StartsWith("session:"))
                {
                    // Session expire → audit log
                    using var scope = _scopeFactory.CreateScope();
                    var audit = scope.ServiceProvider.GetRequiredService<IAuditService>();
                    await audit.LogSessionExpiredAsync(key);
                }
                else if (key.StartsWith("lock:"))
                {
                    // Orphan lock temizlendi — monitoring metric
                    _logger.LogWarning("Lock expired (possible timeout): {Key}", key);
                }
            });

        // Cancellation bekle
        await Task.Delay(Timeout.Infinite, ct);
    }
}

// DI kayıt
builder.Services.AddHostedService<KeyExpirationListener>();

Keyspace notification at-most-once'tır. Client disconnect anında gelen event kaybolur. Kritik iş logic'i için güvenme — sadece monitoring/audit amaçlı kullan. Garantili expiration logic için polling (SCAN + TTL check) tercih et.