0%
Redis Deep Dive: Encodings, Memory, and Failover
The Server Model
Data Structures Inside
Memory Management
Replication and Cluster
Correct Use
The Append-Only File, and What fsync Actually Promises
The append-only file is a log of everything that changed the dataset, written in the same protocol clients use, and its design decisions are unusually easy to reason about once you know that it records effects rather than instructions.
A log of commands in RESP
Every write command that modified something is appended to the file as a RESP array, exactly as a client would have sent it. The file is therefore a replayable transcript, and loading it means feeding those commands through the normal execution path as though a client had issued them.
That choice keeps the mechanism small: there is no second serialisation format, no separate parser, and no risk of the log and the command implementation drifting apart. The cost is that loading is as slow as executing, which is the reason an AOF loads several times slower than an RDB of the same dataset, and the reason Redis keeps both.
Effects, not instructions
A command whose result depends on anything other than its arguments is rewritten before being logged. SPOP becomes SREM with the element that was actually removed. EXPIRE becomes PEXPIREAT with an absolute timestamp. INCRBYFLOAT becomes SET with the computed result. A command with a random or time-dependent outcome never reaches the file in its original form.
The principle is that a replay must reproduce the dataset exactly, and a log of instructions cannot promise that when the instructions are not deterministic. This is the same rule the replication stream follows, for the same reason, and it is one of the most consequential small decisions in Redis: it is why an AOF replay and a replica arrive at identical datasets rather than merely similar ones.
Only what changed
A command that did not modify anything is not logged. DEL on a missing key, SREM of an element that was not in the set, SET with NX on an existing key: all of them return their result to the client and leave the file untouched.
This is worth knowing because it breaks an assumption people make when reading the file for auditing purposes, which is that the AOF is a record of what clients asked for. It is not; it is a record of what happened. A workload that is largely conditional writes that mostly fail produces a small AOF and a large amount of traffic, and the gap between the two is not a bug.
Expired and evicted keys appear as deletions
When a key expires or is evicted, an explicit DEL or UNLINK is appended, because a replay that relied on the clock would produce a different dataset depending on when it ran. The same logic that governs replication governs this.
So a mass expiry writes a million deletions into the AOF, which is the third place in this course that the midnight problem shows up: it is a latency event in the expiry cycle, a burst in the replication stream, and a burst of AOF writes. Jittering TTLs is a one-line change that improves all three, which is a good argument for doing it even when none of the three is currently a problem.
Why the file is larger than the dataset
An RDB of a 10GB dataset is smaller than 10GB. An AOF of the same dataset is usually larger, often much larger, because a key written a thousand times appears a thousand times. The file records history, not state.
That unbounded growth is the central practical problem of the AOF and the reason the rewrite mechanism exists. Without it the file grows with the write volume rather than the data volume, so an instance holding a megabyte of counters that are incremented constantly produces gigabytes of log. Everything in the third section of this lesson is about containing that.
The multi-part AOF
Redis 7.0 replaced the single file with a directory, appenddirname, containing a manifest, a base file, and one or more incremental files. The base file is an RDB snapshot by default; the incremental files are the command log since that base.
The manifest lists the parts and their types, which turns a set of loose files into something whose completeness can be checked. That is the quiet improvement in the design: before it, a truncated or missing file was indistinguishable from a legitimate one, and now the manifest says what should be there.
Why a base in RDB format
Making the base an RDB snapshot means the bulk of the dataset loads at RDB speed and only the recent tail replays as commands. On an instance whose rewrite ran an hour ago, that is hundreds of megabytes a second for nearly all of it and command replay for a small remainder.
This formalises something that existed as aof-use-rdb-preamble from 4.0, which embedded an RDB at the head of the single AOF file. The 7.0 version is cleaner because the parts are separate files with a manifest rather than one file with two formats inside it, and it is the reason the RDB and AOF decision is no longer really two strategies.
Where the writes actually go
Commands are appended to an in-memory buffer during command execution and written to the file in the event loop's beforeSleep, once per iteration. So the write to the operating system happens at the end of the event loop pass, not inside the command.
This matters for reasoning about what is lost in a crash, because the window is not per command but per event loop iteration, and an iteration may contain many commands. It also means the AOF write is a single larger write call rather than many small ones, which is a substantial throughput difference and another example of the event loop structure paying off in a place that looks unrelated.
The AOF on a replica
A replica maintains its own AOF from the commands it receives, independently of whether its primary has one. So a primary with no persistence and a replica with AOF enabled is a perfectly coherent arrangement, and it is the one recommended at the end of this lesson.
What a replica's AOF records is the propagated stream, which is already in effects-not-instructions form, so no further rewriting is needed on arrival. It therefore contains the primary's expiry and eviction deletions as explicit commands, which is why a replica's log can grow from activity the replica itself never originated.
What to read on an unfamiliar instance
INFO persistence carries aof_enabled, aof_base_size, aof_current_size, aof_pending_rewrite, aof_last_bgrewrite_status, aof_last_write_status and aof_rewrite_in_progress. The first tells you whether any of the rest matters, and the ratio of current to base size tells you how close a rewrite is.
I read aof_last_write_status first in an incident, because it is the one that says whether writes to the file are currently succeeding, and a failing value there is both an urgent problem and an easily missed one. aof_last_bgrewrite_status is second, for the same reason as its RDB counterpart.
The AOF records what happened rather than what was asked for. A command that modified nothing is never logged, so DEL on a missing key, SREM of an absent element and SET NX on an existing key all leave the file untouched. That breaks the assumption people make when reading the file for auditing: a workload of mostly-failing conditional writes produces a small AOF and a large amount of traffic, and the gap is not a bug.