5 min read
Redis Persistence Options in Azure: RDB and AOF Explained
Redis persistence ensures your in-memory data survives restarts and failures. Azure Cache for Redis supports both RDB (point-in-time snapshots) and AOF (Append Only File) persistence, available on Premium tier.
Understanding Persistence Options
- RDB (Redis Database): Point-in-time snapshots at configured intervals
- AOF (Append Only File): Logs every write operation for replay
- RDB + AOF: Combined for both fast recovery and minimal data loss
Configuring RDB Persistence
# Create Redis with RDB persistence
az redis create \
--resource-group myResourceGroup \
--name myredispersistent \
--location eastus \
--sku Premium \
--vm-size P1 \
--enable-non-ssl-port false \
--minimum-tls-version 1.2
# Configure RDB backup
az redis update \
--resource-group myResourceGroup \
--name myredispersistent \
--set redisConfiguration.rdb-backup-enabled=true \
--set redisConfiguration.rdb-backup-frequency=60 \
--set redisConfiguration.rdb-storage-connection-string="<storage-connection-string>"
Using ARM Template:
{
"type": "Microsoft.Cache/Redis",
"apiVersion": "2020-12-01",
"name": "myredispersistent",
"location": "eastus",
"properties": {
"sku": {
"name": "Premium",
"family": "P",
"capacity": 1
},
"redisConfiguration": {
"rdb-backup-enabled": "true",
"rdb-backup-frequency": "60",
"rdb-storage-connection-string": "[parameters('storageConnectionString')]"
},
"enableNonSslPort": false,
"minimumTlsVersion": "1.2"
}
}
Configuring AOF Persistence
# Enable AOF persistence
az redis update \
--resource-group myResourceGroup \
--name myredispersistent \
--set redisConfiguration.aof-backup-enabled=true \
--set redisConfiguration.aof-storage-connection-string-0="<primary-storage-connection>" \
--set redisConfiguration.aof-storage-connection-string-1="<secondary-storage-connection>"
Understanding the Tradeoffs
# Python - Demonstrating persistence behavior
import redis
import time
from datetime import datetime
class PersistenceDemo:
def __init__(self, host, port=6380, password=None):
self.redis = redis.StrictRedis(
host=host,
port=port,
password=password,
ssl=True,
decode_responses=True
)
def demonstrate_rdb_behavior(self):
"""
RDB Characteristics:
- Snapshots taken at intervals (e.g., every 60 minutes)
- Minimal performance impact during backup
- Potential data loss = data written since last snapshot
- Faster restart times (single file to load)
"""
# Write data
for i in range(1000):
self.redis.set(f"rdb_test:{i}", f"value_{i}")
# If server crashes before next snapshot, this data could be lost
print(f"Wrote 1000 keys at {datetime.now()}")
print("If RDB backup interval is 60 min, up to 60 min of data could be lost")
def demonstrate_aof_behavior(self):
"""
AOF Characteristics:
- Every write operation logged
- Higher durability (can configure fsync frequency)
- Larger file size than RDB
- Slower restart (replays all operations)
"""
# AOF fsync policies:
# - always: fsync after every write (safest, slowest)
# - everysec: fsync every second (good balance)
# - no: let OS decide (fastest, least safe)
# Each write is appended to AOF
start = time.time()
for i in range(1000):
self.redis.set(f"aof_test:{i}", f"value_{i}")
elapsed = time.time() - start
print(f"Wrote 1000 keys in {elapsed:.2f}s")
print("With AOF, maximum data loss is last second of writes (everysec policy)")
def get_persistence_info(self):
"""Get current persistence configuration"""
info = self.redis.info('persistence')
return {
'rdb_last_save_time': info.get('rdb_last_save_time'),
'rdb_last_bgsave_status': info.get('rdb_last_bgsave_status'),
'rdb_current_bgsave_time_sec': info.get('rdb_current_bgsave_time_sec'),
'aof_enabled': info.get('aof_enabled'),
'aof_current_size': info.get('aof_current_size'),
'aof_last_rewrite_time_sec': info.get('aof_last_rewrite_time_sec'),
'loading': info.get('loading')
}
Managing Backups and Recovery
// C# - Managing Redis backups
using Azure.ResourceManager.Redis;
public class RedisBackupManager
{
private readonly RedisResource _redis;
private readonly string _storageAccountConnectionString;
public async Task TriggerManualBackupAsync()
{
// Export current data to storage
var exportData = new ExportRDBParameters
{
Prefix = $"backup-{DateTime.UtcNow:yyyyMMdd-HHmmss}",
Container = _storageAccountConnectionString
};
await _redis.ExportDataAsync(WaitUntil.Completed, exportData);
}
public async Task ImportBackupAsync(string backupPath)
{
// Import data from storage backup
var importData = new ImportRDBParameters
{
Files = new[] { backupPath }
};
await _redis.ImportDataAsync(WaitUntil.Completed, importData);
}
public async Task ScheduleBackupRetention()
{
// Implement backup rotation
var backups = await ListBackupsAsync();
var cutoffDate = DateTime.UtcNow.AddDays(-30);
var oldBackups = backups.Where(b => b.CreatedAt < cutoffDate);
foreach (var backup in oldBackups)
{
await DeleteBackupAsync(backup.Name);
}
}
}
Monitoring Persistence Performance
// Node.js - Monitor persistence metrics
const Redis = require('ioredis');
class PersistenceMonitor {
constructor(config) {
this.redis = new Redis({
host: config.host,
port: 6380,
password: config.password,
tls: { servername: config.host }
});
}
async getPersistenceMetrics() {
const info = await this.redis.info('persistence');
const lines = info.split('\r\n');
const metrics = {};
lines.forEach(line => {
const [key, value] = line.split(':');
if (key && value) {
metrics[key] = value;
}
});
return {
// RDB metrics
rdb: {
lastSaveTime: new Date(parseInt(metrics.rdb_last_save_time) * 1000),
changesSinceLastSave: parseInt(metrics.rdb_changes_since_last_save),
lastBgsaveStatus: metrics.rdb_last_bgsave_status,
lastBgsaveDuration: parseInt(metrics.rdb_last_bgsave_time_sec),
inProgress: parseInt(metrics.rdb_current_bgsave_time_sec) !== -1
},
// AOF metrics
aof: {
enabled: metrics.aof_enabled === '1',
rewriteInProgress: metrics.aof_rewrite_in_progress === '1',
currentSize: parseInt(metrics.aof_current_size),
baseSize: parseInt(metrics.aof_base_size),
pendingRewrites: parseInt(metrics.aof_pending_rewrite),
lastRewriteDuration: parseInt(metrics.aof_last_rewrite_time_sec)
},
// Loading status (during recovery)
loading: {
inProgress: metrics.loading === '1',
loadedBytes: parseInt(metrics.loading_loaded_bytes) || 0,
totalBytes: parseInt(metrics.loading_total_bytes) || 0,
progress: metrics.loading_total_bytes
? (parseInt(metrics.loading_loaded_bytes) / parseInt(metrics.loading_total_bytes) * 100).toFixed(2)
: 0
}
};
}
async monitorBackgroundSave(callback) {
const checkInterval = setInterval(async () => {
const metrics = await this.getPersistenceMetrics();
callback(metrics);
if (metrics.rdb.inProgress) {
console.log('Background save in progress...');
}
}, 1000);
return () => clearInterval(checkInterval);
}
}
// Usage
const monitor = new PersistenceMonitor(config);
monitor.monitorBackgroundSave((metrics) => {
if (metrics.rdb.changesSinceLastSave > 10000) {
console.log('Warning: Many changes since last save');
}
});
Persistence Best Practices
# Configuration recommendations based on use case
# E-commerce session store
session-store:
persistence: RDB
rdb-backup-frequency: 60 # hourly
rationale: "Sessions can be recreated; RDB provides good balance"
# Financial transaction cache
financial-cache:
persistence: AOF
aof-fsync: everysec
rationale: "Minimize data loss for monetary data"
# Gaming leaderboard
leaderboard:
persistence: RDB + AOF
rdb-backup-frequency: 360 # every 6 hours
rationale: "Fast recovery (RDB) + minimal loss (AOF)"
# Temporary computation cache
temp-cache:
persistence: none
rationale: "Data is ephemeral and can be recomputed"
Best Practices
- Choose based on requirements: RDB for speed, AOF for durability
- Use separate storage accounts: For primary and secondary AOF
- Monitor save times: Long saves indicate performance issues
- Test recovery procedures: Regularly verify backup integrity
- Size storage appropriately: AOF files grow over time
Redis persistence transforms an in-memory cache into a durable data store, providing the reliability needed for critical applications while maintaining Redis’s performance characteristics.