Ch. 27 · Redis

Redis Persistence: RDB vs AOF and fsync Trade-offs

How RDB snapshots and the append-only file work, how much data each appendfsync setting can lose, and how to enable AOF without losing data.

~8 min readintermediateupdated Oct 6, 2026

“Redis is in-memory, so what happens to the data when it restarts?” leads straight to the persistence question: “explain RDB and AOF, and which would you use?” Interviewers then push on numbers (“how much data can you lose with everysec?”) and on operations (“why does memory spike during a snapshot?”). The topic separates candidates who have only used Redis as a cache from those who have run it as a data store.

This guide covers persistence in Redis 7.4 and 8.x, where the AOF format changed significantly compared with older versions. Valkey shares the Redis 7 design.

Before you start

You should know that write() hands data to the operating system’s page cache and fsync() forces it to the disk, so data that was written but not fsynced can vanish on power loss. You should also know what fork() does on Linux: it creates a child process that initially shares all memory pages with the parent, copying a page only when one side modifies it (copy-on-write). Basic redis-cli use is enough for the commands.

The short answer

Redis has two persistence mechanisms. RDB writes point-in-time snapshots: BGSAVE forks a child that writes the dataset to a compact binary file, so restarts are fast and backups are easy, but you lose all writes since the last snapshot. AOF appends every write command to a log; appendfsync decides durability: always (fsync per write batch), everysec (the default, lose about a second) or no (the OS decides, often around 30 seconds). AOF files are compacted by background rewrites. For data that matters I enable AOF with everysec and keep RDB snapshots for backups; for a pure cache I may disable persistence entirely.

How it works

RDB snapshots

# redis.conf (Redis 7 default rules)
save 3600 1 300 100 60 10000     # after 1 change in 1 h, 100 in 5 min, or 10,000 in 1 min
dbfilename dump.rdb
stop-writes-on-bgsave-error yes  # refuse writes if the last background save failed
Terminal

When a rule triggers (or you run BGSAVE), Redis calls fork(). The child writes the dataset to a temporary file and atomically renames it over dump.rdb; the parent keeps serving clients. Thanks to copy-on-write, the child sees a frozen view of memory, and only pages the parent modifies during the save are duplicated. SAVE does the same work on the main thread and blocks everyone, so it is only for maintenance windows.

The append-only file

With appendonly yes, every write command is appended to the AOF after it executes, and replayed on restart. Since Redis 7.0 the AOF is multi-part, stored in appenddirname (default appendonlydir):

appendonlydir/
  appendonly.aof.1.base.rdb     # snapshot at the last rewrite (RDB format by default)
  appendonly.aof.1.incr.aof     # commands since that snapshot
  appendonly.aof.manifest       # which files make up the current AOF
Text

A rewrite (BGREWRITEAOF, or automatic when the AOF has grown by auto-aof-rewrite-percentage 100 since the last rewrite and is at least auto-aof-rewrite-min-size 64mb) forks a child that writes a new base file while the parent starts a new incremental file. When the child finishes, the manifest is updated atomically and old files are deleted.

The fsync policies

appendfsync When data reaches disk Loss on power failure Cost
always fsync after each batch of writes, before replies essentially none (group commit) slowest; disk latency on every write
everysec (default) background thread fsyncs once per second about 1 second of writes small
no when the kernel flushes up to around 30 s on typical Linux lowest

If both AOF and RDB are enabled, Redis loads the AOF at startup because it is the more complete record.

Step-by-step walkthrough

Step 1: Inspect the current persistence state

redis-cli CONFIG GET save
redis-cli CONFIG GET appendonly
redis-cli INFO persistence
# rdb_changes_since_last_save:1520
# rdb_last_bgsave_status:ok
# aof_enabled:0
# aof_rewrite_in_progress:0
# aof_last_write_status:ok
Terminal

rdb_changes_since_last_save is literally how many writes a crash would lose with RDB alone. rdb_last_bgsave_status:err combined with stop-writes-on-bgsave-error yes is why a full disk can suddenly make Redis reject writes with a MISCONF error.

Step 2: Take and check a snapshot

redis-cli BGSAVE
# Background saving started
redis-cli INFO persistence | grep -E 'rdb_bgsave_in_progress|rdb_last_bgsave_status'
redis-cli INFO stats | grep latest_fork_usec     # how long fork() blocked the main thread
redis-check-rdb /var/lib/redis/dump.rdb          # validates the file
Terminal

Copy finished RDB files off the host for backups: the file is only replaced once complete, so copying it at any time is safe.

Step 3: Enable AOF on a live server

The safe sequence, from the Redis documentation, converts the data while the server runs:

redis-cli CONFIG SET appendonly yes      # starts a rewrite that writes the base file
redis-cli INFO persistence | grep -E 'aof_rewrite_in_progress|aof_rewrite_scheduled|aof_last_bgrewrite_status'
# wait for 0, 0 and ok
redis-cli CONFIG REWRITE                 # persist appendonly yes into redis.conf
Terminal

Only after the rewrite completes and redis.conf matches is a restart safe.

Step 4: Pick the fsync policy from the data’s value

For a session store or queue where a second of loss is acceptable, keep everysec. For a ledger-like workload, always costs a disk round trip per write batch; at that point consider whether Redis should be the system of record at all, or whether you need WAITAOF (Redis 7.2) to wait until the write is fsynced locally and on replicas:

SET order:991 paid
WAITAOF 1 1 100      # wait up to 100 ms for local fsync and 1 replica fsync
# 1) (integer) 1
# 2) (integer) 1
Terminal

Step 5: Limit the cost of fork

Both snapshotting and rewrites fork. Fork time grows with memory size (page tables are copied), and copy-on-write can add memory up to the size of the pages written during the save. Keep instances moderately sized, disable transparent huge pages, set vm.overcommit_memory = 1 so fork does not fail when memory looks overcommitted, and consider running persistence on a replica instead of the primary.

Worked scenario

A team ran a Redis 6.2 primary as the store for user preferences with RDB snapshots only. After a crash lost 40 minutes of changes, they decided to enable AOF. An engineer edited redis.conf to set appendonly yes and restarted the service during a maintenance window.

# What they did
sed -i 's/^appendonly no/appendonly yes/' /etc/redis/redis.conf
systemctl restart redis
redis-cli DBSIZE
# (integer) 0
Terminal

With appendonly yes, Redis loads the AOF instead of dump.rdb. No AOF existed yet, so Redis started from an empty AOF, ignored the snapshot, and the replicas then synchronized to the empty dataset. The documentation still warns that changing the config and restarting can lose data on current versions. They recovered from the off-host RDB backup by restoring it with AOF disabled, then followed the live procedure:

# What they should have done
redis-cli CONFIG SET appendonly yes
# wait until aof_rewrite_in_progress:0 and aof_last_bgrewrite_status:ok
redis-cli CONFIG REWRITE
redis-cli DBSIZE      # same count as before
Terminal

They also added a runbook step: compare DBSIZE before and after any restart, and keep the latest RDB off-host.

Common mistake

  • “AOF means zero data loss.” Only with appendfsync always, and even then replication to replicas is asynchronous.
  • “RDB is a full backup, so persistence is solved.” You lose everything since the last snapshot.
  • “everysec blocks the main thread every second.” The fsync runs in a background thread; the main thread only stalls if the disk is so slow that the previous fsync is still running.
  • Running a primary with persistence off and automatic restart. It restarts empty, and replicas faithfully copy the empty dataset. Either keep persistence on or prevent automatic restarts.
  • Calling SAVE in production. It blocks the server for the duration of the snapshot; use BGSAVE.
  • Copying AOF files during a rewrite for backup. The set may be inconsistent; pause automatic rewrites or back up the RDB instead.

Verify the behavior

On a test instance, prove what survives a crash under each setting:

redis-cli CONFIG SET save ""
redis-cli CONFIG SET appendonly no
redis-cli SET survivor 1
redis-cli DEBUG CRASH-AND-RECOVER   # needs enable-debug-command; crashes and restarts without saving
redis-cli GET survivor       # (nil): no persistence

redis-cli CONFIG SET appendonly yes      # wait for the rewrite to finish
redis-cli SET survivor 2
redis-cli DEBUG CRASH-AND-RECOVER
redis-cli GET survivor       # "2": replayed from the AOF
ls /var/lib/redis/appendonlydir/          # base, incr and manifest files
Terminal

redis-check-aof --fix repairs a truncated incremental file after a crash, and Redis loads a truncated tail by default (aof-load-truncated yes) with a warning in the log.

Follow-up questions

What happens when the disk fills up? RDB saves fail and, with stop-writes-on-bgsave-error yes, writes are rejected with MISCONF; AOF write errors also make Redis refuse writes until it can write again.

Why is AOF rewrite needed? The log records every command, so a counter incremented a million times has a million entries; the rewrite writes the current state instead.

Can replicas replace persistence? They protect against machine loss but not against a bad command (FLUSHALL replicates instantly) or a whole-region outage. Backups need snapshots stored elsewhere.

What does aof-use-rdb-preamble do? It writes the base file in RDB format, which is smaller and faster to load, with incremental changes appended in AOF format.

Interview exercise

A Redis primary holds 30 GB of user session and cart data, with appendonly yes, appendfsync always, and RDB snapshots every 5 minutes. Writes are slow (p99 of 15 ms), and memory briefly reaches 52 GB every few minutes. What is causing each problem, and what would you change?

Answer and reasoning

appendfsync always puts a disk fsync in front of every reply, which explains the slow writes; sessions and carts tolerate about a second of loss, so everysec is the right trade-off and removes the disk from the request path. The memory spikes come from fork() copy-on-write: frequent snapshots of a 30 GB write-heavy dataset duplicate many pages, and transparent huge pages make each copy 2 MB instead of 4 KB. Disable THP, drop the frequent RDB schedule on the primary (the AOF already provides durability, and its rewrites use an RDB base), take snapshots for backups on a replica instead, and leave memory headroom. If the dataset keeps growing, splitting it into smaller shards also shortens fork time.

Continue learning

Practise with the Redis interview questions and the Redis MCQs. Related notes: Redis replication, Sentinel and Cluster hash slots for what replicas add on top of persistence, and Operating-system page cache and I/O measurements for what fsync actually guarantees. Primary sources: Redis persistence, the Redis configuration reference, and Diagnosing latency issues, which covers fork latency and transparent huge pages.

More in Redis

esc