SAVE is called.
Snapshot vs AOF
| Property | AOF | Snapshot (.rdbx) |
|---|---|---|
| Captures | Every individual write | Full state at one moment |
| Durability | Near-zero data loss | Loses all writes since last SAVE |
| Recovery | Automatic on startup | Manual LOAD required |
| File grows with | Write volume | Current key count |
| Use case | Continuous durability | Backups, migrations, fast restore |
Creating and loading snapshots
.rdbx to the filename you provide. SAVE mybackup writes mybackup.rdbx in the current working directory.
RDBX binary format
The.rdbx format is a compact, sequential binary file:
expires_at field carries the same semantics as the in-memory Entry: 0 means the key never expires, any other value is an absolute Unix timestamp.
Saving a snapshot
persistence.c
ht_save creates (or overwrites) filename and serializes the entire hash table:
Open the file
Call
fopen(filename, "wb"). If this fails — for example, due to a permissions error or a missing directory — the function returns 0.Write the entry count
Write a
uint32_t containing ht->count. The loader uses this to know how many entries to read.Serialize each entry
Walk every bucket and every chain. For each entry, write:
uint32_t klen— length of the key stringklenbytes — the keyuint32_t vlen— length of the value stringvlenbytes — the valuetime_t expires_at— the absolute expiry timestamp
ht_save does not call fsync. Snapshot writes trade the full durability guarantee of AOF writes for throughput. The file is consistent when fclose returns, but a concurrent power loss during fclose may produce a partial file.Loading a snapshot
persistence.c
RdbStatus, defined as:
persistence.h
Open the file
Call
fopen(filename, "rb"). If the file does not exist or cannot be opened, return RDB_ERR_OPEN.Validate the magic header
Read 5 bytes and compare to
"RDBX1". If they do not match, return RDB_ERR_MAGIC. This catches truncated files, files from a different tool, or accidental loads of the wrong path.Replace the hash table
Free the existing
*ht with ht_free. Allocate a fresh table with ht_create(8) — the minimum size, which grows as entries are loaded.Deserialize each entry
For each of the
count entries:- Read
uint32_t klen, thenklenbytes into a key buffer. Null-terminate. - Read
uint32_t vlen, thenvlenbytes into a value buffer. Null-terminate. - Read
time_t expires_at. - Call
ht_set(*ht, key, value, expires_at)to insert the entry.
ht_set handles its own resizing as the table fills. The loaded hash table ends up in exactly the same logical state as when it was saved.Error handling
| Status | Cause | State after |
|---|---|---|
RDB_OK | File loaded successfully | Hash table replaced with snapshot contents |
RDB_ERR_OPEN | File not found or permission denied | Hash table unchanged |
RDB_ERR_MAGIC | Header mismatch (wrong file or corrupt) | Hash table freed and replaced with empty table |
TTL semantics in snapshots
Theexpires_at field stored in each entry is an absolute Unix timestamp, not a duration. This means:
- A key with
expires_at = 1748000000will expire at that wall-clock time regardless of when the snapshot was taken or loaded. - If the snapshot was created while a key had 5 minutes remaining, and the snapshot is loaded 10 minutes later, that key is already expired at load time.
- Expired keys are not filtered out during
ht_load. They are inserted into the hash table with their originalexpires_at. The passive expiry inht_getand the active sweeper inexpire_sweepwill remove them on next access or next sweep.
Relationship to AOF
Loading a snapshot does not affect the AOF. After aLOAD:
- The in-memory state matches the snapshot.
- The AOF still points to its existing file.
- New
SETandDELcommands continue to append to the AOF as normal. - The AOF and the in-memory state are now out of sync — the AOF contains the history from before the
LOAD, not the snapshot contents.