Skip to content
Back to Blog
2 min read

Redis Persistence Options in Azure: RDB and AOF Explained

Redis persistence is the configuration choice that determines what happens to your cache data when the instance restarts—and for most Azure Cache for Redis use cases, the answer should be “it’s a cache, losing it is fine, the application will warm it up again.” But for use cases where Redis stores data that’s expensive to rebuild (computed aggregates, session state that would log users out, rate limiting counters), Premium tier persistence changes the trade-off. RDB persistence takes point-in-time snapshots at configurable intervals—fast recovery, potential for data loss equal to the interval between snapshots. AOF (Append Only File) logs every write operation—minimal data loss on restart, but slower recovery and more I/O overhead. For most caching workloads, no persistence is the right choice; for stateful use cases, RDB with reasonable snapshot intervals is a practical middle ground.

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

  1. Choose based on requirements: RDB for speed, AOF for durability
  2. Use separate storage accounts: For primary and secondary AOF
  3. Monitor save times: Long saves indicate performance issues
  4. Test recovery procedures: Regularly verify backup integrity
  5. 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.\n\n## Takeaways\n\nAdd a concise, personal takeaway and recommended next steps here.\n

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.