0%
Redis Deep Dive: Encodings, Memory, and Failover
The Server Model
Data Structures Inside
Memory Management
Replication and Cluster
Correct Use
Snapshots, and the Fork That Pays For Them
An RDB file is a compact binary encoding of the entire dataset at one instant, and almost everything interesting about Redis persistence follows from the fact that it is point-in-time rather than continuous.
The shape of the file
The file opens with the magic string REDIS followed by a four-digit version number, then a sequence of opcodes: auxiliary metadata fields, a database selector, optional hints about hash table sizes, then key and value pairs, each tagged with the value's type and encoding. It ends with an end-of-file opcode and an eight-byte CRC64 checksum.
The design priority is clear from the layout: it is compact and sequential, written in one pass and read in one pass, with no index and no ability to look up a single key. That is the right shape for something whose only two jobs are being written during a save and being read in its entirety during a load, and it is why an RDB file is smaller than the memory it represents while an AOF is usually larger.
Values are stored in their memory encoding
A value is written in a form close to how it is held in memory, so a listpack-encoded hash is written as a listpack and a hashtable-encoded one as a sequence of field and value pairs. The encoding is part of the type tag, which is why the file is compact: the memory optimisations of the previous lessons carry straight into the file.
This has a consequence worth knowing. A dataset dominated by small collections in their packed encodings produces a file dramatically smaller than the same logical data held as individual keys, because the packed encodings have almost no per-element overhead and the per-key overhead is absent entirely. The same arrangement that saves memory saves disk and shortens the save, which is unusual: optimisations rarely line up in three places at once.
Compression, and when to turn it off
String values longer than 20 bytes are compressed with LZF unless rdbcompression is set to no. LZF is chosen for being extremely fast rather than for compressing well, which is the correct priority when the compressor runs inside a save that is already costing a fork.
Turning compression off trades a larger file for less CPU during the save, and the case for it is narrow: a dataset of incompressible values, such as encrypted or already-compressed blobs, where LZF spends cycles to achieve nothing. For ordinary text-shaped data compression is worth keeping, often halving the file, and the CPU it costs is paid by the child process rather than the main thread, which makes it close to free from the perspective that matters.
The checksum, and the one reason to disable it
A CRC64 checksum over the whole file is written at the end and verified on load unless rdbchecksum is no. Verification costs a pass over the file, which on a large dataset is seconds, and the protection it buys is detecting a truncated or corrupted file before loading garbage into memory.
I would never turn it off. The CPU is trivial beside the cost of discovering corruption after a restore rather than during one, and a save or a replica synchronisation that silently loads a damaged file is the kind of failure that gets diagnosed days later as mysterious missing data. A checksum of zero is written when the feature is disabled, and load then skips verification, which is also how older files are tolerated.
Auxiliary fields, and what they tell you
The auxiliary opcodes carry metadata: the Redis version that wrote the file, the creation timestamp, the architecture bit width, used memory at the time of writing, and the replication identifier and offset. redis-check-rdb prints them, and they are the first thing to read when identifying an unlabelled file.
The replication identifier and offset are the functionally important ones, because they are what allows a replica loading this file to continue from the right point in the stream rather than starting over. That is the mechanism behind partial resynchronisation surviving a restart, and it is a good example of metadata in a file format earning its place rather than being documentation.
The file is also the replication payload
An RDB snapshot is what a replica receives during a full synchronisation. There is no separate wire format: the primary produces the same bytes it would write to disk and sends them, either via a file or straight down the socket.
That single design decision is why RDB is not optional in the way it first appears. An instance with every save point removed and AOF as its durability mechanism still forks and still produces RDB content every time a replica synchronises from scratch, so the fork cost that this lesson is about cannot be configured away, only moved. People who believe they have turned snapshots off are usually surprised by a fork during a replica attach.
Version compatibility, in both directions
The RDB version increments when the format changes, and a newer Redis reads older files happily while an older Redis refuses a newer file outright. Every new encoding introduced in the memory lessons, listpacks replacing ziplists for instance, bumped the version.
This matters at exactly one moment: a downgrade. Rolling a version back after it has written a snapshot means the older binary cannot load the file, and the data is reachable only by rolling forward again. Taking a copy of the RDB before an upgrade is the cheap insurance, and it is the step most often skipped because upgrades usually go fine.
Loading, and why it is fast
Loading an RDB is a sequential read that builds structures directly, with no command parsing, no replay and no intermediate representation. On ordinary hardware it runs at hundreds of megabytes a second, so a 10GB dataset is a couple of minutes.
Compare that with AOF loading, which replays every command through the normal execution path and is several times slower for the same logical content. That difference is the strongest argument for keeping RDB enabled even on an AOF instance, and it is why Redis 7.0's multi-part AOF keeps a base RDB file: the fast path for the bulk and the log for the recent tail.
Hints the file carries for the loader
Since Redis 7.0 the file records the expected size of each database's main and expires hash tables before the keys arrive, so the loader can allocate them once at the right size instead of growing and rehashing repeatedly as it fills. That is a pure loading optimisation with no effect on correctness.
It is a nice illustration of how much of a file format's value is in what it tells the reader rather than in what it stores. Without the hint, loading a ten million key database means a sequence of rehashes, each copying the whole table, and the loader is doing work the writer already knew how to avoid. The hint costs a handful of bytes and removes it.
What the format cannot do
There is no incremental RDB. Every save writes the whole dataset, because there is no mechanism for identifying what changed since the last one and no structure in the file to append to. A 50GB instance writes 50GB every time, however little has changed.
That is the fundamental limit of the approach and it sets the whole economics of snapshotting: the cost is proportional to the dataset, the benefit is a durability point, and the ratio gets worse as the dataset grows. Everything in the rest of this lesson is about managing that ratio, and the answers are all about where and how often the fork happens rather than about making the file smaller.
An RDB snapshot is also the replication payload, with no separate wire format: the primary produces exactly the bytes it would write to disk and sends them. So an instance with every save point removed and AOF for durability still forks and still produces RDB content whenever a replica synchronises from scratch. The fork cost cannot be configured away, only moved, and people who believe they have turned snapshots off are usually surprised by a fork during a replica attach.