Skip to content

Latest commit

 

History

History
607 lines (422 loc) · 18.6 KB

File metadata and controls

607 lines (422 loc) · 18.6 KB

Chapter 15 — Persistence

Part IV — Operations & Administration

Chapter 14 — Real-World Patterns | Table of Contents | Chapter 16 — Replication →


Redis is fast because it keeps working data in memory. Persistence is the bridge between that speed and the reality that processes crash, hosts reboot, and disks fail. This chapter covers RDB point-in-time snapshots, AOF append-only logs, hybrid persistence introduced in Redis 7, operational procedures for BGSAVE and AOF rewrite, and production backup and restore runbooks.

The guiding question for every recipe: If this Redis process dies right now, how much data can I afford to lose, and how fast must I recover?


Recipe 15.1 — Choose RDB, AOF, or Hybrid

Problem

You are deploying Redis as more than a pure cache. You need a durability strategy that matches your Recovery Point Objective (RPO) and Recovery Time Objective (RTO) without crushing write throughput.

Solution

Redis offers three persistence modes:

Mode Mechanism Typical RPO Recovery speed Write overhead
RDB Periodic fork + snapshot to dump.rdb Minutes (save interval) Fast (single file load) Low between saves; spike during BGSAVE
AOF Append every write to appendonly.aof 0–1 second (appendfsync) Slower (replay log) Continuous; rewrite compacts
Hybrid (Redis 7+) AOF with RDB preamble Same as AOF Faster than pure AOF Best of both for restarts

Decision matrix:

Pure cache, reconstructible data     → disable both (save "" / appendonly no)
Warm cache + acceptable minute loss  → RDB only
Durability-sensitive, moderate load  → AOF everysec
High durability + fast restarts      → Hybrid AOF (Redis 7+)

Minimal RDB-only config:

# redis.conf — RDB snapshots
save 900 1      # save if ≥1 key changed in 15 min
save 300 10     # save if ≥10 keys changed in 5 min
save 60 10000   # save if ≥10k keys changed in 1 min
dbfilename dump.rdb
dir /var/lib/redis
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes

AOF-only config:

appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-use-rdb-preamble yes   # hybrid: RDB header + AOF tail (Redis 7+)
dir /var/lib/redis

Verify active settings at runtime:

127.0.0.1:6379> CONFIG GET save
127.0.0.1:6379> CONFIG GET appendonly
127.0.0.1:6379> INFO persistence

Key INFO persistence fields:

rdb_last_save_time:1700000000
rdb_last_bgsave_status:ok
aof_enabled:1
aof_last_rewrite_time_sec:12
aof_current_size:1048576

Discussion

RDB captures a consistent point-in-time image. Between saves, all writes since the last snapshot are lost on crash. AOF captures every mutating command (with optional fsync), so loss is bounded by the fsync policy.

Hybrid persistence (AOF with RDB preamble) stores an RDB snapshot at the start of the AOF file, followed by incremental AOF entries. On restart, Redis loads the RDB section quickly, then replays the tail—dramatically faster than replaying a multi-gigabyte pure AOF log.

Do not enable both blindly without understanding interaction: with appendonly yes, Redis still creates RDB snapshots if save directives are set, but restarts prefer AOF when both exist.

See Also


Recipe 15.2 — Trigger and Monitor BGSAVE

Problem

You need an on-demand snapshot before maintenance, a config change, or a backup window—without blocking client writes.

Solution

Manual snapshot (non-blocking):

127.0.0.1:6379> BGSAVE
Background saving started
127.0.0.1:6379> LASTSAVE
(integer) 1704067200
127.0.0.1:6379> INFO persistence
# rdb_bgsave_in_progress:1
# rdb_last_bgsave_status:ok
# rdb_last_cow_size:8388608

Blocking snapshot (maintenance window only):

127.0.0.1:6379> SAVE
OK

SAVE blocks all clients until complete. Use only when traffic is drained.

Monitor progress via system tools:

# Watch child process and CoW memory growth
ps aux | grep redis-rdb-bgsave
cat /proc/$(pgrep -f redis-rdb-bgsave)/status | grep VmRSS

# Redis logs show completion
tail -f /var/log/redis/redis-server.log
# Background saving terminated with success

Operational runbook — pre-maintenance snapshot:

#!/bin/bash
# pre-maintenance-bgsave.sh
REDIS_CLI="redis-cli -h 127.0.0.1 -p 6379 -a "$REDIS_PASSWORD" --no-auth-warning"

# 1. Confirm replication caught up (if replica exists)
$REDIS_CLI INFO replication | grep master_repl_offset

# 2. Trigger BGSAVE
$REDIS_CLI BGSAVE

# 3. Poll until complete (max 10 min)
for i in $(seq 1 60); do
  IN_PROGRESS=$($REDIS_CLI INFO persistence | awk -F: '/rdb_bgsave_in_progress/{print $2}' | tr -d '\r')
  if [ "$IN_PROGRESS" = "0" ]; then
    STATUS=$($REDIS_CLI INFO persistence | awk -F: '/rdb_last_bgsave_status/{print $2}' | tr -d '\r')
    echo "BGSAVE finished: $STATUS"
    exit 0
  fi
  sleep 10
done
echo "TIMEOUT: BGSAVE still running"
exit 1

Disable automatic saves temporarily:

127.0.0.1:6379> CONFIG SET save ""
127.0.0.1:6379> CONFIG REWRITE   # persist if redis.conf is writable

Discussion

BGSAVE uses fork(). The child writes the RDB; the parent continues serving writes. Copy-on-write (CoW) means memory usage can spike during BGSAVE if write volume is high—plan headroom equal to dataset size plus CoW overhead.

stop-writes-on-bgsave-error yes (default) rejects writes if the last BGSAVE failed (disk full, permissions). This protects you from running without durable snapshots while believing you have them.

On Linux, enable overcommit or tune vm.overcommit_memory:

sysctl vm.overcommit_memory=1

Without this, large forks can fail with "Can't save in background: fork: Cannot allocate memory" even when physical RAM appears sufficient.

See Also

  • Recipe 15.5 — Backup strategies
  • Recipe 15.6 — Restore procedures

Recipe 15.3 — Configure AOF fsync Policies

Problem

You enabled AOF but need to understand the durability vs. latency trade-off of appendfsync.

Solution

Three policies:

Policy Behavior Durability Performance
always fsync after every write Strongest (~every write durable) Slowest
everysec fsync at most once per second ≤1 second loss on crash Recommended default
no OS decides when to flush Weakest Fastest
appendonly yes
appendfsync everysec
no-appendfsync-on-rewrite no

Runtime inspection:

127.0.0.1:6379> CONFIG GET appendfsync
1) "appendfsync"
2) "everysec"
127.0.0.1:6379> INFO persistence
# aof_pending_bio_fsync:0
# aof_delayed_fsync:0

aof_delayed_fsync incrementing indicates the fsync thread is falling behind—often a sign of disk saturation.

Test write latency impact (rough benchmark):

redis-benchmark -h 127.0.0.1 -p 6379 -a "$REDIS_PASSWORD" \
  -t set -n 100000 -d 256 --appendfsync everysec

# Compare with always (expect significant throughput drop)
redis-cli CONFIG SET appendfsync always
redis-benchmark -h 127.0.0.1 -p 6379 -a "$REDIS_PASSWORD" \
  -t set -n 10000 -d 256
redis-cli CONFIG SET appendfsync everysec

Discussion

With everysec, Redis acknowledges writes after they hit the AOF buffer; fsync happens asynchronously each second. A crash between fsyncs loses at most ~1 second of writes. This is acceptable for most session, queue, and cache-aside workloads.

always is appropriate for financial counters or audit logs where every acknowledged write must survive a power loss. Measure before committing—throughput can drop 10× or more on rotational disks.

no-appendfsync-on-rewrite yes reduces fsync pressure during AOF rewrite by deferring fsync. Slightly increases risk during rewrite; use only when disk I/O is the bottleneck and you accept the trade-off.

See Also


Recipe 15.4 — Manage AOF Rewrite

Problem

Your AOF file has grown to multiple gigabytes. Restarts take too long, and disk usage is climbing.

Solution

AOF rewrite compacts the log by rewriting the current dataset as a fresh set of commands (or as an RDB preamble in hybrid mode), not by replaying every historical command.

Automatic rewrite triggers:

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-use-rdb-preamble yes

Rewrite starts when current AOF size ≥ min-size AND size ≥ percentage growth since last rewrite.

Manual rewrite:

127.0.0.1:6379> BGREWRITEAOF
Background append only file rewriting started
127.0.0.1:6379> INFO persistence
# aof_rewrite_in_progress:1
# aof_rewrite_scheduled:0

Monitor rewrite:

# During rewrite, a temp file appears
ls -lh /var/lib/redis/appendonly.aof*
# appendonly.aof
# appendonly.aof.1.base.rdb   (hybrid base, after rewrite)
# appendonly.aof.1.incr.aof   (incremental tail)

redis-cli INFO persistence | grep aof_rewrite

Multi-part AOF (Redis 7+):

Redis 7 introduced a multi-file AOF format:

appenddirname "appendonlydir"

The directory contains manifest.aof, base RDB files, and incremental AOF segments. Do not manually edit these files; always use Redis tools or controlled copy while stopped.

Runbook — scheduled rewrite during low traffic:

#!/bin/bash
REDIS_CLI="redis-cli -a "$REDIS_PASSWORD" --no-auth-warning"

BEFORE=$($REDIS_CLI INFO persistence | awk -F: '/aof_current_size/{print $2}' | tr -d '\r')
echo "AOF size before: $BEFORE bytes"

$REDIS_CLI BGREWRITEAOF

while [ "$($REDIS_CLI INFO persistence | awk -F: '/aof_rewrite_in_progress/{print $2}' | tr -d '\r')" = "1" ]; do
  sleep 5
done

AFTER=$($REDIS_CLI INFO persistence | awk -F: '/aof_current_size/{print $2}' | tr -d '\r')
echo "AOF size after: $AFTER bytes"

Discussion

Rewrite also uses fork + CoW. During rewrite, new writes accumulate in an rewrite buffer. If rewrite takes long and write rate is high, memory pressure increases.

Hybrid mode (aof-use-rdb-preamble yes) makes post-rewrite restarts much faster—the base file is binary RDB, not thousands of SET commands.

If rewrite fails repeatedly, check disk space, permissions on dir, and dmesg for OOM killer activity on the child process.

See Also

  • Recipe 15.1 — Hybrid persistence
  • Recipe 15.5 — Backup strategies

Recipe 15.5 — Production Backup Strategies

Problem

You need backups that survive operator error, datacenter loss, and ransomware—without corrupting live files.

Solution

Golden rules:

  1. Never copy dump.rdb or live AOF files while Redis is writing them without coordination.
  2. Prefer BGSAVE completion or replica snapshot for consistent copies.
  3. Encrypt backups at rest; test restores quarterly.

Strategy A — RDB after BGSAVE (single primary):

#!/bin/bash
# backup-rdb.sh
set -euo pipefail

BACKUP_DIR="/backups/redis/$(date +%Y%m%d)"
DATA_DIR="/var/lib/redis"
REDIS_CLI="redis-cli -a "$REDIS_PASSWORD" --no-auth-warning"

mkdir -p "$BACKUP_DIR"

$REDIS_CLI BGSAVE
while [ "$($REDIS_CLI INFO persistence | awk -F: '/rdb_bgsave_in_progress/{print $2}' | tr -d '\r')" = "1" ]; do
  sleep 2
done

cp "$DATA_DIR/dump.rdb" "$BACKUP_DIR/dump.rdb"
sha256sum "$BACKUP_DIR/dump.rdb" > "$BACKUP_DIR/dump.rdb.sha256"

# Optional: upload to object storage
# aws s3 cp "$BACKUP_DIR/dump.rdb" s3://my-bucket/redis/

Strategy B — Backup from replica (recommended for production):

Take snapshots on a replica with replica-priority 0 (never promoted) to avoid impacting the primary:

# on dedicated backup replica
replica-priority 0
save 3600 1
appendonly no

Run BGSAVE on the replica; copy its RDB without touching the primary.

Strategy C — AOF directory copy (Redis 7 multi-part):

# Stop replica briefly for consistent copy, or use redis-cli SHUTDOWN SAVE on replica
redis-cli -h replica-host -a "$REDIS_PASSWORD" BGSAVE
# wait for completion
systemctl stop redis-backup-replica
tar czf appendonlydir-$(date +%F).tar.gz -C /var/lib/redis appendonlydir/
systemctl start redis-backup-replica

Strategy D — Continuous object storage with versioned RDB:

Automate hourly BGSAVE + S3 upload with lifecycle policies (30-day retention, cross-region replication).

Verify backup integrity:

# Offline check — start temp instance
mkdir /tmp/redis-verify
cp /backups/redis/20260115/dump.rdb /tmp/redis-verify/
redis-server --port 16379 --dir /tmp/redis-verify --daemonize yes
redis-cli -p 16379 DBSIZE
redis-cli -p 16379 SHUTDOWN NOSAVE

Discussion

Live file copy without BGSAVE risks a partial/corrupt RDB—Redis may fail to start or load truncated data. cp during active write is safe only immediately after BGSAVE completes and before significant new writes (still prefer atomic copy from completed snapshot).

For Cluster deployments, you need backups of all masters taken at roughly the same time, or accept point-in-time inconsistency across slots. Managed services handle this; self-hosted Cluster requires orchestration (see Chapter 18).

See Also


Recipe 15.6 — Restore Procedures

Problem

A host failed, data was corrupted, or you need to roll back to a known-good snapshot.

Solution

Runbook — full restore from RDB (single instance):

#!/bin/bash
# restore-rdb.sh — USE WITH CAUTION: overwrites live data
set -euo pipefail

BACKUP_FILE="/backups/redis/20260115/dump.rdb"
DATA_DIR="/var/lib/redis"
REDIS_USER="redis"

# 1. Stop Redis
systemctl stop redis-server

# 2. Backup current data (in case rollback needed)
mv "$DATA_DIR/dump.rdb" "$DATA_DIR/dump.rdb.bak.$(date +%s)" 2>/dev/null || true
rm -f "$DATA_DIR/appendonly.aof" "$DATA_DIR/appendonlydir" 2>/dev/null || true

# 3. Install backup
cp "$BACKUP_FILE" "$DATA_DIR/dump.rdb"
chown "$REDIS_USER:$REDIS_USER" "$DATA_DIR/dump.rdb"
chmod 660 "$DATA_DIR/dump.rdb"

# 4. Start with AOF disabled initially (load RDB)
# Ensure redis.conf: appendonly no (temporarily)
systemctl start redis-server

# 5. Verify
redis-cli -a "$REDIS_PASSWORD" --no-auth-warning DBSIZE
redis-cli -a "$REDIS_PASSWORD" --no-auth-warning INFO keyspace

Point-in-time recovery with AOF:

If you have a sequence of AOF files (or a single AOF), place the file in dir and start Redis:

systemctl stop redis-server
cp /backups/appendonly.aof /var/lib/redis/appendonly.aof
chown redis:redis /var/lib/redis/appendonly.aof
# redis.conf: appendonly yes, save ""
systemctl start redis-server
# Redis replays AOF on startup — watch logs for progress

Partial restore (specific keys) — use redis-cli --rdb:

# Export live RDB without stopping (runs BGSAVE internally)
redis-cli -a "$REDIS_PASSWORD" --rdb /tmp/live.rdb

# Or from backup file, use redis-rdb-tools / rdb --command json
# pip install rdbtools
rdb --command json /backups/dump.rdb | jq 'select(.key | startswith("user:"))'

Disaster recovery checklist:

[ ] Identify last known-good backup (verify sha256)
[ ] Drain or redirect application traffic
[ ] Stop Redis cleanly if possible (SHUTDOWN SAVE)
[ ] Restore files with correct ownership
[ ] Start Redis; confirm DBSIZE and sample keys
[ ] Re-enable replication (REPLICAOF / resync replicas)
[ ] Re-enable AOF if disabled for restore
[ ] Resume traffic; monitor error rates and replication lag

Emergency — redis-check-aof / redis-check-rdb:

redis-check-rdb /var/lib/redis/dump.rdb
redis-check-aof /var/lib/redis/appendonly.aof
# Fix AOF (creates .fix file — review before use)
redis-check-aof --fix /var/lib/redis/appendonly.afo

Discussion

Restoring onto a running primary in a replication topology requires care: wipe replicas and re-sync after primary restore, or promote a replica that was ahead (risky). Standard pattern: restore primary from backup, then REPLICAOF NO ONE if needed, reset replicas with REPLICAOF primary 6379.

AOF --fix truncates corrupted tail; you may lose commands after the corruption point. Always keep the original file.

For Sentinel-managed environments (Chapter 17), pause failover during restore or ensure the restored node is recognized as the legitimate primary before Sentinels re-elect.

See Also


Recipe 15.7 — Persistence Health Monitoring

Problem

You need alerts before failed BGSAVEs, runaway AOF growth, or disk exhaustion take down writes.

Solution

Key metrics to scrape from INFO persistence:

rdb_last_bgsave_status          → must be "ok"
rdb_last_bgsave_time_sec        → trend for duration spikes
aof_last_bgrewrite_status       → must be "ok"
aof_current_size                → growth rate
aof_base_size                   → post-rewrite baseline
loading                         → 1 during startup replay

Prometheus-style alert rules (conceptual):

# alerts/redis-persistence.yaml
- alert: RedisBgsaveFailed
  expr: redis_rdb_last_bgsave_status != 1  # 1=ok in some exporters
  for: 5m
  labels:
    severity: critical
  annotations:
    summary: "Redis BGSAVE failed on {{ $labels.instance }}"

- alert: RedisAofSizeGrowing
  expr: increase(redis_aof_current_size[24h]) > 1e9
  for: 1h
  labels:
    severity: warning

Disk space check:

df -h /var/lib/redis
# Alert when >80% full; BGSAVE needs temp space ≈ dataset size

Daily health script:

redis-cli INFO persistence | grep -E 'rdb_last|aof_last|aof_current|loading'
redis-cli CONFIG GET dir
redis-cli CONFIG GET appenddirname
du -sh /var/lib/redis/

Discussion

loading:1 during startup on a large AOF can last minutes—load balancers should health-check accordingly. Use redis-cli --intrinsic-latency 100 separately for latency baseline (Chapter 20).

Combine persistence monitoring with replication lag (Chapter 16): a replica with appendonly yes adds I/O load that can delay fsync on the primary if misconfigured on the same disk.

See Also


Next: Chapter 16 — Replication →