Modelled on src/rdb.c and src/rdb.h, Redis 7.2.14. Listpack, intset and CRC64 layouts follow listpack.c, intset.c and crc64.c from the same release.
An RDB file is a header, a run of opcodes, one record per key and an 8-byte checksum.
Build a keyspace below and every byte a 7.2.14 server would write for it appears on the
right, in order. The bytes are real: the download is a loadable .rdb, and the
type byte for each value is chosen the way rdbSaveObjectType chooses it, so a
small hash gets a listpack and a large one gets a hash table.
No keys. Redis skips an empty database entirely — no SELECTDB opcode is written for it (rdb.c:1310).
FF magic and opcode FF length or encoding prefix FF payload FF end of file FF CRC64
The complete 7.2.14 list. An opcode is read where a value type byte would otherwise be,
so anything from 245 upward is an instruction rather than a type
(rdbIsObjectType covers 0–7 and 9–21, rdb.h:100).
| hex | dec | name | what follows it |
|---|---|---|---|
| F5 | 245 | FUNCTION2 | One function library's source, as a string. One opcode per library (rdb.c:1279). |
| F6 | 246 | FUNCTION_PRE_GA | “Old function library data for 7.0 rc1 and rc2” (rdb.h:104). |
| F7 | 247 | MODULE_AUX | Module id, a UINT sub-opcode and a when-marker, module data, then an EOF sub-opcode (rdb.c:1216). |
| F8 | 248 | IDLE | A length-encoded idle time in seconds. Written only under an LRU maxmemory policy (rdb.c:1136–1140). |
| F9 | 249 | FREQ | One raw byte, the LFU counter. Written only under an LFU policy (rdb.c:1144–1152). |
| FA | 250 | AUX | Two strings: field name, then value. |
| FB | 251 | RESIZEDB | Two lengths: key count, then how many of them have an expiry. |
| FC | 252 | EXPIRETIME_MS | 8 bytes, little-endian, an absolute Unix time in ms. |
| FD | 253 | EXPIRETIME | 4 bytes read as an int32, in seconds (rdb.c:134). Still read, never written by 7.2 — rdb.c:1130 always uses FC. |
| FE | 254 | SELECTDB | A length-encoded database number. |
| FF | 255 | EOF | Nothing. The 8-byte checksum follows. |
Per-key metadata precedes the value type byte, not the key:
FC <8 bytes> then F8/F9 if the policy asks for
them, then the type byte, then the key, then the value (rdb.c:1129–1158).
RDB_OPCODE_SLOT_INFO does not exist in 7.2.
The top two bits of the first byte say how to read the rest.
| first byte | meaning |
|---|---|
| 00xxxxxx | The length is the low 6 bits. One byte total, covering 0–63. |
| 01xxxxxx | 14-bit length: low 6 bits are the high half, the next byte the low half. |
| 10000000 | RDB_32BITLEN (0x80). A 32-bit length follows, big-endian — htonl, unlike every other multi-byte field in the format. |
| 10000001 | RDB_64BITLEN (0x81). A 64-bit big-endian length follows. Taken only when the length exceeds UINT32_MAX (rdb.c:196–199). |
| 11xxxxxx | RDB_ENCVAL. Not a length: the low 6 bits name a special encoding for the string that follows. |
A string is tried as an integer first, then as compressed, then stored verbatim.
| prefix | layout |
|---|---|
| C0 | INT8: one signed byte. |
| C1 | INT16: two bytes, little-endian. |
| C2 | INT32: four bytes, little-endian. |
| C3 | LZF: compressed length, then original length, both length-encoded, then the payload (rdb.c:345–361). |
| — | Otherwise a length, then that many raw bytes. |
Integer encoding is attempted only when the string is at most 11 bytes (rdb.c:447) and
parses as a canonical integer — string2ll rejects leading zeros,
-0 and trailing text (util.c:438). Compression is attempted only when
rdbcompression is on and the string is longer than 20 bytes (rdb.c:457);
this page never compresses, so a long string here shows the verbatim form.
The type byte names the encoding on disk, not the user-visible type. A small collection is one packed blob; a large one is written element by element. The ziplist types 10, 12 and 13 are still read by 7.2 but nothing writes them any more.
| value | type byte | written when | payload |
|---|---|---|---|
| String | 0 STRING | always | One string. |
| List | 18 LIST_QUICKLIST_2 | always — there is no other list branch (rdb.c:665–668) | Node count, then per node a container flag (2 = packed) and the node's listpack as a string. |
| Set | 11 SET_INTSET | every member parses as an integer and there are at most 512 (set-max-intset-entries) | An intset blob as a string. |
| Set | 20 SET_LISTPACK | at most 128 members, none longer than 64 bytes | A listpack as a string. |
| Set | 2 SET | otherwise | Member count, then each member as a string. |
| Hash | 16 HASH_LISTPACK | at most 512 fields, no field or value longer than 64 bytes | A listpack of field, value, field, value. |
| Hash | 4 HASH | otherwise | Field count, then each field and value as strings. |
| Sorted set | 17 ZSET_LISTPACK | at most 128 members, none longer than 64 bytes | A listpack of member, score, member, score, in ascending score order. |
| Sorted set | 5 ZSET_2 | otherwise | Member count, then each member and its 8-byte score — walked from the skiplist tail, so highest score first (rdb.c:914–925). |
| Stream | 21 STREAM_LISTPACKS_3 | always | Not modelled here. |
Thresholds are the shipped defaults from config.c:3236–3244. A list stays one
listpack node until that node would exceed 8192 bytes, the limit
list-max-listpack-size = -2 selects (quicklist.c:49, quicklist.c:467).
Functions (F5), module data (F7), streams, LZF compression, the
LRU and LFU opcodes, and databases other than 0. The two hash-table branches
(2 SET and 4 HASH) are written here in insertion order; a
server writes them in dict iteration order, which is neither insertion order nor
reproducible between runs. Everything else is the byte sequence Redis 7.2.14 would
produce, including the CRC64, so redis-check-rdb accepts the download and
a server loads it.