Disaster Recovery, Backup, and Point-in-Time Restore12 min read
Module A-13·12 min read
RDB backup scheduling, AOF log shipping to cold storage, BGSAVE + BGREWRITEAOF interaction, DEBUG RELOAD for in-memory consistency checks, and a documented recovery runbook for the three most common failure scenarios.
A-13 — Disaster Recovery, Backup, and Point-in-Time Restore
Who this module is for: Redis is holding data that matters — sessions, job queues, rate limit state, coordination locks. If the server dies, you need to recover. This module covers RDB backup scheduling, AOF log shipping, recovery runbooks for the three most common failure scenarios, and the one practice most teams skip: testing restore procedures before they need them.
The Backup Strategy Pyramid
Not all Redis data needs the same protection:
Data Type
Recovery Strategy
RTO
RPO
Pure cache
Regenerate from DB on miss
Seconds
Any (cache is disposable)
Session store
Restore from RDB + accept some logouts
Minutes
Last snapshot
Job queue
Restore from RDB + reprocess in-flight jobs
Minutes
Last snapshot
Rate limit state
Restore from RDB + accept burst on restart
Minutes
Last snapshot
Event log / audit trail
AOF log shipping + S3
< 30 min
< 1 minute
RPO (Recovery Point Objective): Maximum acceptable data loss (time). RTO (Recovery Time Objective): Maximum acceptable downtime (time to recover).
Design your persistence configuration to meet the RPO; design your restore procedure to meet the RTO.
RDB Backup Scheduling
Automated Periodic Snapshots
bash
Add to cron:
text
Backup Retention Policy
text
Implement with S3 Lifecycle policies:
json
AOF Log Shipping
For near-real-time backup (RPO < 1 minute), ship the AOF file to durable storage continuously:
bash
For Redis 7.0+ multi-part AOF (RDB base + incremental AOF files):
bash
BGSAVE and BGREWRITEAOF Interaction
If you're shipping both RDB snapshots and AOF files as backup layers, know what happens when they overlap: Redis will not run BGSAVE and BGREWRITEAOF at the same time. If a BGREWRITEAOF is already in progress and a scheduled BGSAVE fires (or vice versa), the second one is deferred — Redis logs a message and starts it automatically once the first finishes, rather than running both fork()s concurrently. This matters for backup scheduling in two ways:
Don't assume your RDB backup cron job and your AOF rewrite trigger are independent. If they collide, your backup script needs to poll (INFO persistence — check rdb_bgsave_in_progress/aof_rewrite_in_progress) rather than assuming the snapshot completed on schedule.
Both operations fork a child process, and both children hold a copy-on-write reference to the dataset. If they did somehow overlap (they don't, by design, but a naive mental model might assume otherwise), you'd risk doubling the memory overhead from copy-on-write page duplication under write load — one more reason Redis serializes them rather than running both at once.
Check current state before triggering either manually:
bash
Recovery Runbooks
Scenario 1: Single Node Crash (Data Loss ≤ Last Snapshot)
Symptoms: Redis process crashed or OOM killed. No failover configured. Data loss: Everything since last successful BGSAVE.
bash
RTO: ~2–10 minutes (time to start Redis + load RDB). RPO: Time since last BGSAVE (up to your snapshot interval).
Scenario 2: Data File Corruption
Symptoms: Redis refuses to start; logs show "Bad file format" or checksum error.
Manual exports to GCS: gcloud redis instances export my-instance gs://my-bucket/dump.rdb
Restore by importing from GCS
Redis Cloud:
Automatic backup to S3/GCS/Azure Blob, configurable frequency (every hour to daily)
Point-in-time restore via the console or API
For managed services: use the platform's backup mechanisms rather than running custom scripts. They handle the coordination with the managed service's storage layer.
Summary
Match backup strategy to data criticality: pure caches need no backup; audit logs need AOF shipping
RDB backup script: BGSAVE → wait for completion → copy dump.rdb → upload to S3 → prune old backups
Recovery runbooks for three scenarios: node crash (restore RDB), corruption (restore from backup), FLUSHALL (restore to test instance, migrate keys)
Test your restore procedure monthly — a backup you have never restored is not a backup
For managed services (ElastiCache, Memorystore, Redis Cloud): use the platform's backup mechanisms
RTO measurement: include it in monthly restore drills to verify you can recover within your SLA
Next: A-14 — Performance Benchmarking and Production Tuning — redis-benchmark, OS-level tuning, slowlog analysis, and the configuration changes that meaningfully improve throughput.
Knowledge Check
You are designing the backup strategy for a Redis instance that acts exclusively as an in-memory session store. A server crash resulting in the loss of the last 5 minutes of session data (forcing those users to log in again) is acceptable, but losing all sessions is not. Which backup strategy offers the best balance of performance and recovery capability for this specific workload?
During a disaster recovery drill, you notice that your automated RDB backup script occasionally uploads corrupted or incomplete .rdb files to S3. Looking at the script, you see it runs BGSAVE and then immediately copies the /var/lib/redis/dump.rdb file. What is the fatal flaw in this script?
A junior developer accidentally runs FLUSHALL on production Redis, instantly deleting all keys. You have an automated RDB backup from 1 hour ago stored in S3. What is the safest and most reliable recovery procedure?
Test your knowledge with more question sets
Sign in to access a wider variety of questions and get notified when new practice sets are added to this module.
#!/bin/bash# /opt/scripts/redis-backup.sh — run via cronREDIS_CLI="/usr/bin/redis-cli"REDIS_HOST="127.0.0.1"REDIS_PORT="6379"BACKUP_DIR="/backups/redis"S3_BUCKET="s3://my-company-backups/redis"DATE=$(date +%Y%m%d-%H%M)# Trigger a background save$REDIS_CLI-h$REDIS_HOST-p$REDIS_PORT BGSAVE
# Wait for save to complete (poll rdb_bgsave_in_progress)while[$($REDIS_CLI -h $REDIS_HOST -p $REDIS_PORT INFO persistence |grep rdb_bgsave_in_progress |cut -d: -f2|tr-d'\r')-eq1];dosleep1done# Check if last save was successfulBGSAVE_STATUS=$($REDIS_CLI -h $REDIS_HOST -p $REDIS_PORT INFO persistence |grep rdb_last_bgsave_status |cut -d: -f2|tr-d'\r\n ')if["$BGSAVE_STATUS"!="ok"];thenecho"BGSAVE failed!">&2exit1fi# Copy the RDB filecp /var/lib/redis/dump.rdb "$BACKUP_DIR/dump-$DATE.rdb"# Upload to S3 with server-side encryptionaws s3 cp"$BACKUP_DIR/dump-$DATE.rdb""$S3_BUCKET/dump-$DATE.rdb"--sse aws:kms
# Verify uploadaws s3 ls"$S3_BUCKET/dump-$DATE.rdb"# Cleanup local backups older than 7 daysfind"$BACKUP_DIR"-name"dump-*.rdb"-mtime +7 -deleteecho"Backup completed: dump-$DATE.rdb"
#!/bin/bash# Ship new AOF data to S3 every minute# Uses inotifywait to detect file changesAOF_PATH="/var/lib/redis/appendonlydir"S3_BUCKET="s3://my-company-backups/redis/aof"inotifywait -m-e close_write "$AOF_PATH"|whileread path action file;do aws s3 sync"$AOF_PATH""$S3_BUCKET/"--exclude"*.tmp"echo"$(date): AOF shipped to S3"done
# Ship only the incremental AOF files (the base is the RDB snapshot)aws s3 sync /var/lib/redis/appendonlydir/ s3://backups/redis/aof/ \--include"appendonly.aof.*.incr.aof"
redis-cli INFO persistence |grep-E"rdb_bgsave_in_progress|aof_rewrite_in_progress"
# 1. Start a fresh Redis instancesystemctl start redis
# The instance loads dump.rdb automatically on startup# Verify:redis-cli INFO persistence |grep rdb_last_save_time
# 2. Verify key count matches expectationsredis-cli DBSIZE
# 3. Warm the cache if needed (re-populate from database)# ... application-specific cache warming ...echo"Recovery complete. Data restored to: $(date-d @$(redis-cli INFO persistence |grep rdb_last_save_time |cut -d: -f2|tr-d'\r'))"
# 1. Check what Redis says about the RDB fileredis-check-rdb /var/lib/redis/dump.rdb
# Output: corrupt? what position?# 2. If RDB is corrupt: restore from backupaws s3 cp s3://backups/redis/dump-20240315-1200.rdb /var/lib/redis/dump.rdb
# 3. Verify the backup fileredis-check-rdb /var/lib/redis/dump.rdb
# 4. Start Redissystemctl start redis
# --- OR if using AOF ---# Check and fix a corrupt AOF fileredis-check-aof --fix /var/lib/redis/appendonlydir/appendonly.aof.1.incr.aof
# This truncates the file at the corruption point (data after the corruption is lost)# Start Redissystemctl start redis
# 1. IMMEDIATELY stop the application from writing to Redis# Update your application config to disable Redis, or put Redis in read-only mode:redis-cli CONFIG SET replica-read-only yes# this doesn't help for the primary# Best: take the Redis primary offline temporarily, start a replica from backup# 2. Find the most recent backup before the FLUSHALL# Check S3 timestamps vs when FLUSHALL was detected# 3. Start a restore instance (do NOT restore on the running primary)# Start a separate Redis instance:redis-server --port6380--dbfilename restore.rdb --dir /tmp/redis-restore
# 4. Copy the backup RDB to the restore instancecp /tmp/dump-backup.rdb /tmp/redis-restore/restore.rdb
# IMPORTANT: plain `DEBUG RELOAD` SAVEs the current (empty) in-memory dataset# to disk FIRST, which overwrites the file you just copied in, before it# reloads — silently destroying the backup you're trying to restore. Use# NOSAVE to skip that save step and just load the file as-is:redis-cli -p6380 DEBUG RELOAD NOSAVE # or restart the instance to load the file# 5. Verify the restore instance has the dataredis-cli -p6380 DBSIZE
redis-cli -p6380 KEYS "sample:*"|head-20# 6. Migrate data from restore to production using redis-cli --piperedis-cli -p6380--scan|whileread key;do redis-cli -p6380 DUMP "$key"| redis-cli -p6379 RESTORE "$key"0 -
done# This is slow for large datasets; use redis-cli --cluster import or a DUMP/RESTORE script# 7. Restore application writes
#!/bin/bash# /opt/scripts/restore-drill.sh — run monthly# Start a test Redis instancedocker run -d--name redis-restore-test -p6381:6379 redis:7-alpine
# Download the most recent backupaws s3 cp s3://backups/redis/dump-latest.rdb /tmp/redis-restore/dump.rdb
# Copy to the test containerdockercp /tmp/redis-restore/dump.rdb redis-restore-test:/data/dump.rdb
# Restart to load the RDBdocker restart redis-restore-test
sleep5# Verify key countKEY_COUNT=$(redis-cli -p6381 DBSIZE)echo"Restore drill: $KEY_COUNT keys restored"# Sample a few known keysredis-cli -p6381 GET "user:1001"redis-cli -p6381 HGETALL "config:global"# Clean updocker stop redis-restore-test
dockerrm redis-restore-test
echo"Restore drill complete at $(date)"