Redis persistence: how much data do you lose on restart or kill -9? Intermediate
Estimated reading time: 9 min read
The problem
Section titled “The problem”A Redis 7.4 instance holds a product cache (reloadable from PostgreSQL), login sessions, and per-IP rate limit counters. Everything lives in RAM; persistence is how Redis writes the dataset to disk so it can reload it on startup.
When the container is kill -9ed or the machine loses power: losing the cache only raises database load, losing sessions logs users out, and losing counters gives an IP an extra window of requests.
The question: with the current configuration, what is left after a Redis crash, and what is the most you can lose?
What to choose? (summary first)
Section titled “What to choose? (summary first)”| Situation | Choose | Condition |
|---|---|---|
| Pure cache | Disable persistence (save "", appendonly no) or sparse RDB |
The database can handle a cold start; with replicas, the master must not auto-restart |
| Sessions, rate limit counters | AOF everysec + keep RDB |
Accept losing about 1 s of writes when the machine crashes |
| Data that must not be lost | Do not make Redis the only source of truth; if unavoidable: AOF always + replication + WAIT |
Throughput drops sharply; failover can still lose acknowledged writes |
The Redis docs recommend enabling both when you need safety close to PostgreSQL’s; RDB is still useful for backups and fast restarts.
Core concepts
Section titled “Core concepts”| RDB | AOF | |
|---|---|---|
| What it writes | A snapshot of the whole dataset (dump.rdb) |
A log of every write command, replayed on startup |
| When | save <seconds> <changes> rules or BGSAVE |
Every write command; fsync per appendfsync |
| Lost on crash | Every write after the last snapshot | Depends on appendfsync (table below) |
| Default in 7.4 | On: save 3600 1 300 100 60 10000 |
Off: appendonly no |
The default save rules: snapshot after 3600 s if ≥ 1 change, after 300 s if ≥ 100, after 60 s if ≥ 10000. Check with CONFIG GET save.
fsync forces the kernel to push data from the page cache to disk. Data that has been write()n but not fsynced survives the process dying, but is lost when the machine crashes.
appendfsync |
Behavior (per the docs) | Lost when the machine crashes |
|---|---|---|
always |
fsync before replying to the client, grouping several commands into one fsync | Almost nothing; very slow |
everysec (default) |
fsync every second on a background thread | About 1 s; up to 2 s if a slow fsync makes Redis delay write() |
no |
The kernel flushes on its own (Linux typically about 30 s) | Depends on the kernel |
How it works
Section titled “How it works”Session B and an INCR command pass through RAM, dump.rdb, and the AOF directory, then Redis dies:
The latest snapshot contains session A. From here on, RDB stays frozen until the next save.
| Point | Memory | dump.rdb |
AOF on disk |
|---|---|---|---|
| Snapshot | A | A | A |
SET B, fsync |
A, B | A | A, B |
INCR |
A, B, rl=1 | A | INCR not yet fsynced |
| Restart | RDB only: A remains | A, B; INCR survives if only the process died |
RDB: fork and copy-on-write
Section titled “RDB: fork and copy-on-write”BGSAVE calls fork(). The child writes the snapshot to a temp file and then renames it over the old file, so dump.rdb is never half-written; the parent keeps serving clients.
Copy-on-write: after the fork, parent and child share memory pages; the kernel copies a page only when the parent modifies it. The more writes during the save, the more RAM grows. Two things to measure:
- Fork latency:
fork()copies the page table on the main thread. Seelatest_fork_usec(INFO stats); the docs cite roughly 9–13 ms/GB on modern hardware. - CoW memory: the
Fork CoW for RDB: ...log line andrdb_last_cow_sizeinINFO persistence. The docs require disabling transparent huge pages, because THP causes nearly all memory to be copied.
AOF: rewrite and multi-part since Redis 7
Section titled “AOF: rewrite and multi-part since Redis 7”100 INCRs produce 100 entries for one key. A rewrite rebuilds the minimal command set: run via BGREWRITEAOF or automatically when the AOF doubles in size since the last rewrite and is ≥ 64 MB (auto-aof-rewrite-percentage 100, auto-aof-rewrite-min-size 64mb).
Since 7.0, the AOF is the appenddirname directory (default appendonlydir):
- base: the snapshot at rewrite time; in RDB format (
.base.rdb) becauseaof-use-rdb-preamble yesis the default. - incr: write commands after the base (
.incr.aof). - manifest: the list of files and their load order.
During a rewrite, the parent keeps writing to a new incr file, the child writes a new base, then Redis swaps the manifest atomically; if the rewrite fails, the old files still hold all the data.
Which file a restart loads
Section titled “Which file a restart loads”Loading diagram…
The docs: when both are enabled, the AOF is loaded because it is the most complete. The “empty AOF” branch was reproduced in the demo.
Operational details
Section titled “Operational details”- Clean shutdown (
SIGTERM): withsaverules, Redis writes an RDB before exiting (demo:docker stopkept all 5000/5000 keys).docker stoponly waits 10 s beforeSIGKILL. stop-writes-on-bgsave-error yes(default): ifBGSAVEfails, for example on a full disk, Redis refuses writes.aof-load-truncated yes(default): an AOF truncated at the end still loads; corruption in the middle needsredis-check-aof.
Replication does not replace persistence
Section titled “Replication does not replace persistence”The replication docs describe it: a master with persistence off crashes and is auto-restarted with an empty dataset; the replicas resync and wipe their copies too. With Sentinel, the master can restart so fast that the failure goes undetected.
Recommendation: enable persistence on both master and replicas; if you can’t, disable auto-restart for the master. Replication is asynchronous; WAIT only lowers the probability of losing writes.
Real-world example
Section titled “Real-world example”Demo: a dedicated Redis 7.4.9 (port 6391), writing batches of 50 SETs continuously for 3 seconds, docker kill right after the last acknowledged batch, restart, count DBSIZE:
npx --prefix examples ts-node examples/src/redis-persistence/crash-demo.ts rdb # also everysec, always, none| Configuration | Client received OK | Left after restart | Lost |
|---|---|---|---|
| No persistence | 589,800 | 0 | 589,800 |
RDB save 60 1000, snapshot at second 60 |
1,058,300 | 411,550 | 646,750 |
AOF everysec |
630,950 | 630,950 | 0 |
AOF always |
183,850 | 183,850 | 0 |
Reading the results:
docker killonly kills the process; data alreadywrite()n is still in the page cache, soeverysecloses 0. Only a power loss exposes the unfsynced part; the demo cannot simulate that.alwaysgot about 3.4 times fewer OKs thaneverysec(Docker Desktop on macOS, relative comparison only; numbers vary per run).
Real output: the AOF directory, enabling AOF the wrong way, fork and CoW
[everysec] after restart: 630950 keys remain, 0 keys lost-rw------- 1 redis redis 22183987 appendonly.aof.2.base.rdb-rw------- 1 redis redis 33363023 appendonly.aof.2.incr.aof-rw------- 1 redis redis 88 appendonly.aof.manifestfile appendonly.aof.2.base.rdb seq 2 type bfile appendonly.aof.2.incr.aof seq 2 type iRestart with --appendonly yes on a volume holding a 5000-key dump.rdb:
* Creating AOF base file appendonly.aof.1.base.rdb on server startDBSIZE → 0A 383 MB dataset, BGSAVE while redis-benchmark writes random SETs:
latest_fork_usec:2475Fork CoW for RDB: current 108 MB, peak 108 MB, average 72 MBTrade-offs
Section titled “Trade-offs”What persistence gives you
- RDB: compact file, fast restart, safe to copy for backup while Redis is running
- AOF
everysec: almost no writes lost when the process dies; about 1 s lost when the machine crashes - Multi-part AOF: rewrites don’t buffer writes in RAM, and an RDB-format base loads fast
The price you pay
- Fork blocks the main thread; CoW needs spare RAM
- AOF
alwayscuts throughput several times over - A slow fsync can cause latency spikes
Production checklist
Section titled “Production checklist”- Separate pure-cache instances from instances holding sessions.
- Sessions, counters:
appendonly yes,appendfsync everysec, keep thesaverules; verify withCONFIG GETafter deploys. - Reserve RAM for CoW, disable transparent huge pages, alert on
latest_fork_usecandrdb_last_cow_size. - Alert when
rdb_last_bgsave_status,aof_last_bgrewrite_status, oraof_last_write_statusis notok. - Switch from RDB to AOF with
CONFIG SET appendonly yes, wait for the rewrite to finish,CONFIG REWRITE, and only then restart. - With replicas: enable persistence on the master or disable its auto-restart.
- Shutdown timeouts (
docker stop -t,terminationGracePeriodSeconds) long enough for the final save. - Copy the RDB or the AOF directory off the machine regularly and test restores.
Common mistakes
Section titled “Common mistakes”The second trap is pairing replication with a master that has no persistence:
| Misconception | Consequence | What to do |
|---|---|---|
| With both enabled, Redis loads the newer file | Restores the wrong data | With AOF present, Redis always loads the AOF |
always means nothing is ever lost |
Still lost on failover to a replica that hadn’t received the write | Keep the source of truth in a transactional database |
Self-check
Section titled “Self-check”- RDB only, default rules, 50 changes written at 10:00,
kill -9at 10:04. What remains after restart? - Why does
docker killwitheveryseclose 0 keys, while a power loss can lose about 1 second? - A 20 GB dataset with heavy writes during
BGSAVE: which metric tells you how much spare RAM to reserve? - Why can a replica with AOF enabled still lose data?
Summary
Section titled “Summary”References
Section titled “References”- Redis persistence — Redis Docs:
appendfsync, multi-part AOF, switching from RDB to AOF. - Redis replication — Redis Docs: a master without persistence wipes replica data.
- Diagnosing latency issues — Redis Docs: fork latency, THP, fsync.
- redis.conf Redis 7.4: persistence defaults.