Open-Source Wikis

/

Redis

/

Systems

/

Persistence

redis/redis

Persistence

Active contributors: antirez, Oran Agra, Binbin, Wen Hui.

Redis persists data through two complementary mechanisms — RDB snapshots and the AOF log — that can be used independently or together. The implementation is heavily fork-based: child processes inherit the parent's memory via copy-on-write and serialise the data while the parent keeps serving.

Source layout

File Role
src/rdb.c RDB writer and reader. ~4,700 lines.
src/rdb.h RDB opcodes, version constants, public API.
src/aof.c AOF writer, AOF rewriter (forked child), multi-part manifest.
src/redis-check-rdb.c Standalone RDB validator (also linked as a callable function).
src/redis-check-aof.c Standalone AOF validator and --fix mode.
src/rio.c, src/rio.h Buffered IO abstraction used by both formats. Knows about file, fd, buffer, conn.
src/chk.c, src/chk.h Checksum helpers used by RDB and the multi-part AOF manifest.
src/childinfo.c Pipe-based reporting from the persistence child to the parent (memory accounted, COW deltas).
src/lzf_c.c, src/lzf_d.c LZF compression for RDB strings.

RDB

An RDB file is a snapshot. The writer walks every database, every key, every value, and serialises them into a single binary file with a header (REDIS<version>), a sequence of records, an EOF opcode, and a CRC64 checksum.

When does an RDB get written

Trigger Caller
SAVE Synchronously, on the main thread (rare; blocks the server).
BGSAVE Forks a child; the child writes the file and exits.
save <seconds> <changes> The cron checks elapsed time and dirty count and triggers BGSAVE automatically.
Replication full sync Either disk-backed (write file, send file) or diskless (stream straight over the socket).
SHUTDOWN [SAVE] Final RDB before exit.

Format

The high-level structure is:

  1. Magic REDIS + 4-char version (currently 0011 for RDB v11).
  2. AUX fields (redis-ver, redis-bits, ctime, used-mem, aof-preamble, …).
  3. Module-defined data type prologue (per loaded module that registered RDB save callbacks).
  4. For each database:
    • SELECT_DB opcode + db id.
    • RESIZEDB opcode + key count + expire count (lets the loader pre-size hash tables).
    • For each key: optional expire opcode, optional idle/freq opcode, type opcode, key as length-prefixed string, value as type-specific payload.
  5. EOF opcode.
  6. CRC64 of everything before.

The opcodes (RDB_OPCODE_*) and type codes (RDB_TYPE_*) are in src/rdb.h. Each version of the format that introduced a new encoding (listpack lists, listpack hashes, listpack sets, stream v2, stream v3, hash with field TTLs, vector sets, …) gets a new type code.

Loading

rdbLoad (in src/rdb.c) is called from the main thread at startup or during DEBUG RELOAD. It pre-sizes the kvstore from the RESIZEDB hint, then deserialises records. Module-registered data types call back via the registered loader.

A failed load aborts startup (a corrupt RDB never silently overwrites the dataset). rdb-check-rdb lets operators verify a file before they roll forward.

AOF

The AOF (Append-Only File) is a write-ahead log of every mutating command. Replay loads the dataset by re-executing the commands in order.

Multi-part AOF (Redis 7+)

A modern AOF is not a single file. It is:

  • A manifest file (appendonly.aof.manifest by default).
  • One base file — either an RDB (*.rdb) or a legacy AOF (*.aof).
  • Zero or more incremental AOF files (*.aof) chained after the base.

The manifest tracks the names, types, and "incremental sequence" numbers. On rewrite, a new base is generated by the child; once finished, the manifest is atomically renamed to point at the new base, and old files are deleted.

The single-file legacy format is still supported for backwards compatibility (when loading); writes always use multi-part.

Rewrite

BGREWRITEAOF forks a child. The child reads the dataset (in COW memory) and writes a new base file. Meanwhile the parent keeps appending mutations to a separate "rewrite buffer" that the child also reads via a pipe. When the child finishes, the parent atomically swaps the manifest.

Rewrite is automatic when auto-aof-rewrite-percentage % of growth is reached relative to the last rewrite size, and at least auto-aof-rewrite-min-size bytes have been written.

Fsync policy

appendfsync always       # fsync after every write — strongest durability, slowest
appendfsync everysec     # fsync once per second from a BIO_AOF_FSYNC thread — default
appendfsync no           # let the OS decide

everysec lets the BIO thread (src/bio.c) handle the fsync without blocking the main loop. The cron in src/server.c and src/aof.c schedules the fsync; the BIO thread executes it.

Loading

loadAppendOnlyFile reads the manifest, then loads the base (calling rdbLoad if it's an RDB, or replaying commands if it's a legacy AOF), then for each incremental in order, opens it and replays. Replay uses the standard command-table dispatch with a special "fake client" that produces no replies.

A truncated tail in the last incremental file is handled gracefully: the load stops at the last valid command and the rest is discarded with a warning. aof-load-truncated yes (default) controls this.

A corruption mid-file aborts the load. redis-check-aof --fix is the operator escape hatch.

RDB-AOF preamble

aof-use-rdb-preamble yes (default) tells the rewriter to emit an RDB body as the first part of a single-file AOF (legacy mode) or as the base file (multi-part mode). This makes rewrites much faster than re-emitting SET commands and uses less disk.

Failure modes

Problem Behaviour Recovery
Disk full mid-write Write is retried; RDB child fails and is restarted next cron. Free disk; manual BGSAVE/BGREWRITEAOF.
AOF fsync error with appendfsync always The command is rolled back if aof-fsync-on-rewrite no and the write is still in the buffer. Otherwise marked dirty and the server may panic depending on aof-error-mode. Fix the disk; resume.
Truncated AOF on startup Loaded up to the last valid command; the rest is ignored. Optionally re-run --fix.
Corrupt RDB on startup Server refuses to start. redis-check-rdb, restore from replica, replay AOF.

Persistence + replication

Replication uses RDB for the full-sync phase. Two modes:

  • Disk-backed — the master forks a child, writes the RDB to disk, then ships the file to the replica.
  • Diskless — the master forks a child that streams the RDB straight over the replica's socket(s).

For diskless, the master can also stream to multiple replicas at once if they connect in a small time window (repl-diskless-sync-delay). The newer rdbchannel mode opens a second TCP connection so the RDB transfer doesn't block live commands.

See Replication for the protocol details.

Where to start modifying

  • Bump the RDB version — add a new RDB_TYPE_* code in src/rdb.h, write a saver in rdbSaveObject, write a loader in rdbLoadObject. Be very careful with backward compatibility.
  • Add an AUX field — write it via rdbSaveAuxField in rdbSaveInfoAuxFields. The loader logs unknown AUX fields and continues.
  • Tweak rewrite schedulingserverCron in src/server.c has the AOF/RDB scheduler block.
  • Improve fsyncflushAppendOnlyFile in src/aof.c. The BIO interface is in src/bio.c.
  • Replication — uses RDB for sync.
  • Memory management — eviction can drop keys after LASTSAVE.
  • ModulesRedisModule_RegisterDataType requires AUX-aware save/load callbacks.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Persistence – Redis wiki | Factory