AOF (Append-Only File)
The Append-Only File (AOF) is one of two persistence mechanisms Redis offers for surviving restarts and crashes without losing data. Instead of periodic snapshots, AOF works like a transaction log: every write command Redis executes gets appended to a file on disk, so replaying that file from an empty keyspace reconstructs the exact same dataset. This lesson covers how AOF actually writes and rewrites data, the durability tradeoffs of its fsync policies, and the mistakes that catch people off guard when they rely on it.
Overview / How it works
Redis has two persistence mechanisms: RDB, which takes point-in-time binary snapshots of the whole dataset, and AOF, which is the subject of this lesson. Rather than dumping the dataset periodically, AOF logs every command that mutates the keyspace — SET, INCR, EXPIRE, DEL, and so on — as it happens. Read-only commands like GET are never logged, since they change nothing. If you know SQL, think of AOF as similar in spirit to a write-ahead log or binlog: a durable, ordered record of every change, which can be replayed to rebuild state.
Because Redis is single-threaded, each command is fully applied to memory before it is ever queued for the AOF. There is no window where a half-executed command could leak into the log — the in-memory mutation and the decision to log it happen as one atomic step from the perspective of any other client.
After a command is applied, Redis appends its representation to an in-memory AOF buffer. That buffer is written out to the operating system and then fsync‘d to physical disk according to the appendfsync policy you’ve configured (always, everysec, or no) — this is the single biggest durability/performance knob for AOF, covered below.
In modern Redis (7.x), AOF is not a single flat file but a directory (commonly appendonlydir) containing a base file (a compact snapshot of the dataset at the time of the last rewrite, itself stored in RDB or AOF format), one or more incr files (the incremental commands written since that base), and a manifest file that lists which base and incr files currently make up the log. This multi-part layout is what makes BGREWRITEAOF safe and cheap: Redis never has to rewrite a single giant file in place.
A rewrite works like this: Redis forks a child process. The child writes a brand-new, minimal base file that reflects the current in-memory dataset — for example, a thousand INCR calls on the same counter collapse into a single command that sets the final value. Meanwhile, the parent process keeps serving traffic normally and buffers any new writes into a fresh incr file. When the child finishes, Redis atomically swaps the manifest to point at the new base and incr files and removes the old ones. On restart, Redis reads the manifest, loads the base, then replays the incr file(s) in order to reconstruct the dataset, including restoring TTLs from absolute expiration timestamps written into the log.
Syntax
AOF is mostly controlled through configuration directives rather than a single dedicated command. The relevant pieces are:
CONFIG SET appendonly <yes|no>
CONFIG SET appendfsync <always|everysec|no>
BGREWRITEAOF
| Command / Directive | Purpose | Time complexity |
|---|---|---|
CONFIG GET appendonly |
Read the current AOF enabled/disabled state | O(1) |
CONFIG SET appendonly yes|no |
Enable or disable AOF at runtime (does not persist across restart on its own) | O(1) to set; toggling on triggers an implicit rewrite, which is O(N) in dataset size |
CONFIG SET appendfsync <policy> |
Choose how aggressively the AOF buffer is fsynced to disk | O(1) |
BGREWRITEAOF |
Manually trigger a background rewrite/compaction of the AOF | O(N) in dataset size (forks and serializes the whole keyspace) |
INFO persistence |
Inspect AOF/RDB state: whether AOF is enabled, rewrite progress, file sizes, last status | O(1) |
The directives that matter most:
- appendonly —
yesorno. Whether AOF logging is active. - appendfsync —
always(fsync after every write, safest and slowest),everysec(fsync roughly once per second in a background thread — the default and the sane middle ground), orno(let the OS decide when to flush — fastest, but a crash can lose whatever the OS hadn’t flushed yet). - auto-aof-rewrite-percentage — trigger an automatic rewrite once the AOF has grown this percentage larger than it was after the last rewrite (default
100, i.e. doubled). - auto-aof-rewrite-min-size — a floor size (default
64mb) below which auto-rewrite won’t trigger, so a tiny dataset doesn’t rewrite constantly.
Examples
Example 1: Check whether AOF is currently enabled.
CONFIG GET appendonly
Output:
1) "appendonly"
2) "no"
By default a fresh Redis instance ships with AOF off (it relies on RDB snapshots instead unless you turn AOF on explicitly). CONFIG GET returns config key/value pairs as a flat array, which is why you see the directive name followed by its value.
Example 2: Write some data and inspect the persistence state.
SET session:abc123 "active"
INCR pageviews:home
EXPIRE session:abc123 3600
INFO persistence
Output (INFO persistence returns dozens of fields; the ones that matter for AOF are shown here):
# Persistence
loading:0
aof_enabled:0
aof_rewrite_in_progress:0
aof_last_bgrewrite_status:ok
aof_last_write_status:ok
rdb_changes_since_last_save:3
rdb_bgsave_in_progress:0
rdb_last_bgsave_status:ok
... (additional fields omitted)
Every write — SET, INCR, EXPIRE — happened normally regardless of whether AOF is on, because AOF is purely a durability layer sitting alongside normal command execution, not something that changes how commands behave. INFO persistence is your primary window into whether AOF is enabled (aof_enabled), whether a rewrite is currently running (aof_rewrite_in_progress), and whether the last rewrite or write succeeded (aof_last_bgrewrite_status, aof_last_write_status). When AOF is active, this section also exposes aof_current_size and aof_base_size, which tell you how much the log has grown since the last rewrite.
Example 3: Enable AOF at runtime and trigger a manual rewrite.
CONFIG SET appendonly yes
BGREWRITEAOF
Output:
OK
Background append only file rewriting started
This is shown for illustration only (it mutates global server configuration, so it isn’t executed against the shared test instance). CONFIG SET appendonly yes turns AOF on immediately and implicitly performs an initial rewrite to seed the base file from the current dataset. BGREWRITEAOF requests an on-demand rewrite at any later point — useful right before a maintenance window, or if you want to shrink the log without waiting for the automatic percentage/size thresholds to trip.
How it works step by step
Walking through a single write once AOF is enabled:
- 1. A client sends a write, e.g.
SET foo bar. - 2. Redis’s single command-processing thread applies it to the in-memory dataset. This step is atomic — no other command can interleave mid-execution.
- 3. The command is appended to an in-memory AOF buffer.
- 4. Depending on
appendfsync: withalways, Redis fsyncs the buffer to disk before acknowledging the write; witheverysec, a background thread fsyncs roughly once per second, so at most ~1 second of writes are at risk on a crash; withno, Redis writes to the OS buffer but leaves fsync timing entirely to the kernel. - 5. As the incr file grows past the
auto-aof-rewrite-percentage/auto-aof-rewrite-min-sizethresholds, Redis automatically performs the equivalent of aBGREWRITEAOF. - 6. During a rewrite, Redis forks a child process that serializes the current dataset into a new, compact base file while the parent keeps handling traffic and buffering concurrent writes into a new incr file.
- 7. Once the child finishes, the manifest is atomically updated to reference the new base and incr files, and the old ones are deleted.
- 8. On restart, Redis reads the manifest, loads the base file, then replays the incr file(s) in order, restoring TTLs from absolute expiration times so a key that was set to expire at a specific moment expires correctly no matter how long the server was down.
Common Mistakes
Mistake 1: Assuming a runtime CONFIG SET appendonly yes survives a restart. Running CONFIG SET appendonly yes turns AOF on for the currently running process, but it does not modify redis.conf on disk. If the server restarts before you’ve persisted the change, it comes back up with AOF off again, silently losing the durability you thought you had. Corrected approach: add appendonly yes and appendfsync everysec directly to redis.conf (or run CONFIG REWRITE after the CONFIG SET, which writes the current runtime config back to the config file) so the setting survives a restart.
Mistake 2: Treating appendfsync no as risk-free just because AOF is enabled. Turning AOF on doesn’t guarantee durability by itself — the appendfsync policy does. With no, Redis writes to the OS page cache and lets the kernel decide when to flush to disk, which on Linux is typically every 30 seconds; a crash or power loss in that window loses all of those writes even though AOF was "on". Corrected approach: use everysec (the default and the right choice for almost all workloads — bounded to roughly one second of potential loss with a small, steady fsync cost), or always only if you can absorb the per-write fsync latency in exchange for zero acknowledged writes ever being lost.
Mistake 3: Disabling automatic rewrites and never triggering one manually. Setting auto-aof-rewrite-percentage to 0 disables automatic compaction. Without a manual BGREWRITEAOF schedule to replace it, the incr file grows without bound, wasting disk and making restarts progressively slower since Redis has to replay the entire, increasingly redundant command history. Corrected approach: leave the automatic thresholds enabled (or set sensible ones), and use INFO persistence‘s aof_current_size and aof_base_size fields to confirm rewrites are actually happening and shrinking the log over time.
Best Practices
- Default to
appendfsync everysecunless you have a specific, tested reason to needalways‘s stronger guarantee. - Run AOF alongside RDB rather than choosing one exclusively — RDB gives you fast, compact snapshots for backups and quick restarts, AOF gives you a much smaller data-loss window; many production setups use both.
- After enabling AOF with
CONFIG SET, persist the change toredis.conf(or runCONFIG REWRITE) so it isn’t lost on the next restart. - Watch
aof_last_bgrewrite_statusandaof_last_write_statusinINFO persistence; anything other thanokmeans writes or rewrites are failing, usually from disk space or permissions issues. - Make sure the volume holding the AOF has headroom of at least twice the current AOF size, since a rewrite briefly needs space for both the old and new files.
- Treat the
appendonlydiras one atomic unit for backups — copy the whole directory (base, incr files, and manifest together), never a single file out of it. - Test restart/recovery from your AOF periodically in a non-production environment to confirm it actually loads cleanly and quickly at your real data size.
Practice Exercises
- Using
CONFIG GET, check whether AOF and whatappendfsyncpolicy are currently active on a Redis instance. Without running it, write down the two separate steps you’d need to take to make anappendonly yeschange survive a server restart. - Write a string key with a TTL, a counter incremented with
INCR, and a couple of other keys, then runINFO persistenceand noteaof_current_size. If AOF were enabled and you triggeredBGREWRITEAOF, what would you expect to happen to that number relative toaof_base_size, and why? - You’re designing persistence for a service doing roughly 50,000 writes/second where losing the last second of writes on a crash is acceptable, but a multi-hundred-millisecond latency spike on every write is not. Decide which
appendfsyncsetting fits, and explain the durability-vs-latency tradeoff that led you there.
Summary
- AOF persists data by logging every write command as it executes, rather than taking periodic snapshots like RDB.
- Because Redis is single-threaded, each command is fully applied before being logged, so the log never contains a partial write.
- The
appendfsyncpolicy (always,everysec,no) controls how much data you can lose on a crash —everysecis the sensible default. - Modern Redis stores AOF as a directory of a base file plus incr files plus a manifest, and
BGREWRITEAOFcompacts them by forking and serializing a fresh, minimal base. - Enabling AOF with
CONFIG SETalone doesn’t survive a restart — persist the change toredis.confor useCONFIG REWRITE. - Use
INFO persistenceto monitor whether AOF is enabled, whether rewrites are succeeding, and how large the log has grown. - AOF and RDB aren’t mutually exclusive — most production deployments benefit from running both.
