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ı.