RRedis Handbook

UZMAN

Persistence — RDB & AOF Detay

Backup'ın gerçekten restore edilebilir olduğunu otomatik doğrula — aylık manuel test yerine scheduled job.

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

Karşılaştırma

RDB (Snapshot) AOF (Append-Only File) Hybrid (Önerilen)
Mekanizma Fork + dump Her komut loglama RDB preamble + AOF tail
Veri kaybı Son snapshot'tan beri fsync policy'ye göre (max 1s) Max 1s
Dosya boyutu Compact Büyük (rewrite ile küçülür) Orta
Restart hızı Hızlı Yavaş (replay) Hızlı
CPU Fork sırasında spike Sürekli düşük Dengeli
# redis.conf — Hybrid (production önerisi)
appendonly yes
aof-use-rdb-preamble yes
appendfsync everysec
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
save 3600 1 300 100 60 10000

# Manuel snapshot
BGSAVE
LASTSAVE

# AOF rewrite tetikle
BGREWRITEAOF

# Durum kontrol
INFO persistence
// Persistence doğrudan .NET'ten yönetilmez — redis.conf ile yapılır.
// Ama persistence durumunu monitoring'e dahil edebilirsin:

public class RedisMonitoringService
{
    private readonly IConnectionMultiplexer _mux;

    public RedisMonitoringService(IConnectionMultiplexer mux)
        => _mux = mux;

    public async Task<PersistenceStatus> GetPersistenceStatusAsync()
    {
        var server = _mux.GetServers().First();
        var info = await server.InfoAsync("persistence");

        var section = info.First(s => s.Key == "persistence");
        var dict = section.ToDictionary(p => p.Key, p => p.Value);

        return new PersistenceStatus
        {
            RdbLastSaveTime = DateTimeOffset.FromUnixTimeSeconds(
                long.Parse(dict["rdb_last_save_time"])).UtcDateTime,
            RdbChangesSinceLastSave = long.Parse(dict["rdb_changes_since_last_save"]),
            AofEnabled = dict["aof_enabled"] == "1",
            AofCurrentSize = long.Parse(dict.GetValueOrDefault("aof_current_size") ?? "0"),
            AofLastRewriteStatus = dict.GetValueOrDefault("aof_last_bgrewrite_status") ?? "unknown"
        };
    }

    // Health check'e persistence durumu ekle
    public async Task<bool> IsPersistenceHealthyAsync()
    {
        var status = await GetPersistenceStatusAsync();
        var lastSave = DateTime.UtcNow - status.RdbLastSaveTime;

        // Son 2 saat içinde snapshot alınmamışsa alarm
        return lastSave < TimeSpan.FromHours(2);
    }
}

Production: Her zaman appendonly yes + aof-use-rdb-preamble yes. Backup: RDB dosyasını S3/blob'a kopyala (cron).

Backup & Restore

# === BACKUP ===
# 1. Manuel snapshot
BGSAVE
LASTSAVE    # Unix timestamp

# 2. RDB dosya konumu
CONFIG GET dir          # /data
CONFIG GET dbfilename   # dump.rdb

# 3. Cron ile S3'e kopyala (her 6 saat)
# crontab:
# 0 */6 * * * aws s3 cp /data/dump.rdb s3://my-backups/redis/dump-$(date +%Y%m%d-%H%M).rdb

# 4. AOF dosyası da backup'la
# /data/appendonly.aof.1.incr.aof (son AOF)

# === RESTORE ===
# 1. Redis'i durdur
redis-cli SHUTDOWN NOSAVE

# 2. RDB'yi yerine koy
cp /backup/dump-20260527-0600.rdb /data/dump.rdb

# 3. Redis'i başlat — otomatik load eder
redis-server /etc/redis/redis.conf

# 4. Doğrula
redis-cli DBSIZE
redis-cli INFO persistence

# === RESTORE TESTİ (aylık) ===
# Ayrı bir container'da restore test et:
docker run -v /backup:/data -p 6399:6379 redis:8-alpine
redis-cli -p 6399 DBSIZE    # key count doğrula
// Backup trigger (admin endpoint)
public class BackupService
{
    private readonly IConnectionMultiplexer _mux;

    public BackupService(IConnectionMultiplexer mux) => _mux = mux;

    public async Task<DateTime> TriggerBackupAsync()
    {
        var server = _mux.GetServers().First();
        await server.SaveAsync(SaveType.BackgroundSave);

        // Son save zamanını al
        var lastSave = await server.LastSaveAsync();
        return lastSave;
    }

    // Health check'te backup freshness kontrolü
    public async Task<bool> IsBackupFreshAsync(TimeSpan maxAge)
    {
        var server = _mux.GetServers().First();
        var lastSave = await server.LastSaveAsync();
        return (DateTime.UtcNow - lastSave) < maxAge;
    }
}

Automated Backup Verification (CI/CD)

// Scheduled backup verification service
// Backup'ı geçici container'a restore eder, key count doğrular
public class BackupVerificationService
{
    private readonly ILogger<BackupVerificationService> _logger;
    private readonly IConfiguration _config;

    public BackupVerificationService(ILogger<BackupVerificationService> logger,
        IConfiguration config)
    {
        _logger = logger;
        _config = config;
    }

    // Haftalık çalıştır (Hangfire, Quartz, veya cron job)
    public async Task<BackupVerificationResult> VerifyLatestBackupAsync()
    {
        // 1. En son backup dosyasını bul (S3/Blob'dan)
        var backupPath = await DownloadLatestBackupAsync();
        if (backupPath is null)
            return new BackupVerificationResult { Success = false, Error = "No backup found" };

        // 2. Testcontainers ile geçici Redis başlat (backup ile)
        var redis = new RedisBuilder()
            .WithImage("redis:8-alpine")
            .WithResourceMapping(backupPath, "/data/dump.rdb")
            .WithCommand("redis-server", "--appendonly", "no")
            .Build();

        try
        {
            await redis.StartAsync();

            using var mux = await ConnectionMultiplexer.ConnectAsync(
                redis.GetConnectionString());
            var server = mux.GetServers().First();
            var db = mux.GetDatabase();

            // 3. Doğrulama kontrolleri
            var dbSize = await server.DatabaseSizeAsync();
            var info = await server.InfoAsync("memory");
            var ping = await db.PingAsync();

            // Key count minimum eşik (production'dan bilinen baseline)
            var minExpectedKeys = long.Parse(
                _config["Backup:MinExpectedKeys"] ?? "1000");

            var result = new BackupVerificationResult
            {
                Success = dbSize >= minExpectedKeys,
                KeyCount = dbSize,
                PingLatency = ping,
                VerifiedAt = DateTime.UtcNow
            };

            if (!result.Success)
                _logger.LogError("Backup verification FAILED: {Keys} keys (expected >={Min})",
                    dbSize, minExpectedKeys);
            else
                _logger.LogInformation("Backup verified OK: {Keys} keys, {Ping}ms ping",
                    dbSize, ping.TotalMilliseconds);

            return result;
        }
        finally
        {
            await redis.DisposeAsync();
        }
    }

    private async Task<string?> DownloadLatestBackupAsync()
    {
        // S3/Blob implementation — projeye göre değişir
        throw new NotImplementedException("Implement per your storage provider");
    }
}

public record BackupVerificationResult
{
    public bool Success { get; init; }
    public long KeyCount { get; init; }
    public TimeSpan PingLatency { get; init; }
    public DateTime VerifiedAt { get; init; }
    public string? Error { get; init; }
}

CI/CD pipeline'a ekle: Haftalık scheduled job olarak çalıştır. Başarısız olursa PagerDuty/Slack alert tetikle. Backup'ın varlığı yetmez — restore edilebilirliği kanıtlanmalı.