Overview
Relevant Files
README.mdMANIFESTOsrc/server.hsrc/server.c
Redis is a high-performance, in-memory data structure server that serves as a cache, database, and message broker. Written in C, it prioritizes speed, simplicity, and efficiency while supporting a rich ecosystem of data types and operations.
Core Philosophy
Redis is built on ten core principles outlined in its manifesto. The project emphasizes memory-first storage, fundamental data structures with clear semantics, code efficiency, and fighting complexity. Rather than using threading, Redis employs a single-threaded event-driven architecture, with horizontal scaling achieved through Redis Cluster and replication.
Key Capabilities
Data Structures: Redis provides native support for strings, lists, sets, sorted sets, hashes, streams, JSON documents, and vector sets. Each data type is optimized for specific use cases—from leaderboards (sorted sets) to real-time messaging (streams).
Processing Engines: The Redis Query Engine enables full-text search, vector similarity search, geospatial queries, and aggregations on indexed documents. Probabilistic data structures like HyperLogLog, Bloom filters, and t-digest support analytics and cardinality estimation.
Persistence: Redis offers two persistence mechanisms—RDB (point-in-time snapshots) and AOF (append-only file)—allowing data durability without sacrificing performance.
Replication & Clustering: Master-replica replication enables high availability, while Redis Cluster provides horizontal scaling across multiple nodes with automatic slot management.
Architecture Highlights
Loading diagram...
The server core (server.c, server.h) manages the global state, client connections, and the main event loop. Commands are dispatched through a command table, with each operation manipulating data stored in memory-resident hash tables and specialized structures.
Performance Characteristics
Redis achieves sub-millisecond latency through in-memory storage and efficient data structures. The event-driven architecture handles thousands of concurrent clients without blocking. Memory management uses jemalloc by default on Linux to minimize fragmentation, and the codebase includes sophisticated eviction policies (LRU, LFU) when memory limits are reached.
Use Cases
- Caching: Fast access to frequently used data with configurable expiration
- Session Storage: Flexible session data modeling with multiple data types
- Real-Time Analytics: Counters, leaderboards, and rate limiting
- Message Queues: Lists, streams, and pub/sub for inter-service communication
- Vector Search: Semantic similarity and RAG applications with vector sets
- Search Engine: Full-text and geospatial indexing via Redis Query Engine
Architecture & Core Components
Relevant Files
src/ae.h– Event loop interface and data structuressrc/ae.c– Event loop implementationsrc/connection.h– Connection abstraction layersrc/networking.c– Client I/O and protocol handlingsrc/server.c– Server initialization and main loop
Redis is built on a single-threaded, event-driven architecture that efficiently handles thousands of concurrent connections. The core design revolves around three key components: the event loop, the connection abstraction, and the networking layer.
Event Loop (ae.c/ae.h)
The event loop is the heart of Redis. It's a multiplexing system that monitors file descriptors and timers, dispatching events as they occur. The aeEventLoop structure tracks:
- File events: Read/write readiness on sockets (registered via
aeCreateFileEvent) - Time events: Periodic tasks like key expiration and background operations
- Fired events: Events ready to process in the current iteration
The loop uses the best available multiplexing API for the platform (epoll on Linux, kqueue on BSD, evport on Solaris, select as fallback). Each iteration calls aeProcessEvents(), which polls for ready file descriptors, executes callbacks, and processes timers.
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
while (!eventLoop->stop) {
aeProcessEvents(eventLoop, AE_ALL_EVENTS|
AE_CALL_BEFORE_SLEEP|
AE_CALL_AFTER_SLEEP);
}
}
Connection Abstraction (connection.h)
The connection structure provides a protocol-agnostic interface for TCP, Unix sockets, and TLS. Each connection holds:
- A
ConnectionTypefunction pointer table (vtable pattern) - State tracking (connecting, accepting, connected, closed, error)
- Bound event loop reference and read/write handlers
- Private data for protocol-specific state
This abstraction allows Redis to support multiple connection types without duplicating I/O logic. Connection types register themselves at startup via connTypeRegister().
Networking Layer (networking.c)
The networking layer handles client protocol parsing, command execution, and response buffering. Key responsibilities:
- Client management: Linking/unlinking clients, tracking connection state
- Protocol parsing: RESP (Redis Serialization Protocol) parsing
- Output buffering: Queuing responses for efficient batch writes
- Memory tracking: Monitoring client output buffer sizes
Integration Flow
Loading diagram...
When a client connects, the accept handler creates a connection object and registers read/write handlers with the event loop. As data arrives, the read handler parses commands and queues responses. The write handler flushes buffered output when the socket becomes writable. This non-blocking, event-driven model allows a single thread to serve thousands of clients efficiently.
Key Design Patterns
- Callback-based: Handlers are registered and invoked asynchronously
- Barrier writes: The
AE_BARRIERflag ensures writes happen after reads in the same iteration, useful for persistence - Before/after sleep hooks:
beforeSleep()performs maintenance (expiration, client cleanup) before blocking on I/O - Lazy event processing: Events are only processed when needed, minimizing CPU usage
Data Structures & Encodings
Relevant Files
src/object.csrc/server.hsrc/t_string.csrc/t_list.csrc/t_set.csrc/t_hash.csrc/t_zset.csrc/t_stream.csrc/quicklist.hsrc/listpack.hsrc/intset.hsrc/stream.h
Core Object Model
Every Redis value is wrapped in a redisObject structure (16 bytes on 64-bit systems) that stores metadata:
struct redisObject {
unsigned type:4; /* OBJ_STRING, OBJ_LIST, OBJ_SET, etc. */
unsigned encoding:4; /* Internal representation format */
unsigned lru:24; /* LRU/LFU eviction data */
unsigned iskvobj:1; /* Embedded key flag */
unsigned expirable:1; /* Has expiration time */
unsigned refcount:30; /* Reference counting */
void *ptr; /* Pointer to actual data */
};
The type field identifies the Redis data type (String, List, Set, Hash, Sorted Set, Stream, or Module). The encoding field specifies how that type is internally represented, allowing Redis to optimize storage based on data characteristics.
Encoding Strategies
Redis uses multiple encodings per type to balance memory efficiency and performance:
- Strings:
RAW(raw SDS),EMBSTR(embedded <44 bytes),INT(integer) - Lists:
QUICKLIST(linked list of compressed listpacks),LISTPACK(compact sequential storage) - Sets:
INTSET(compact integer storage),LISTPACK(small sets),HT(hash table for large sets) - Hashes:
LISTPACK(small hashes),HT(hash table for large hashes) - Sorted Sets:
LISTPACK(small sets),SKIPLIST(skip list + hash table for large sets) - Streams:
STREAM(radix tree of listpacks)
Compact Data Structures
Listpack is a memory-efficient sequential container storing strings and integers with variable-length encoding. Each entry includes length metadata, enabling O(1) random access while maintaining compact storage.
Intset stores integers in a sorted array with adaptive encoding (16-bit, 32-bit, or 64-bit) based on value ranges. When a value outside the current encoding range is added, the entire intset is upgraded.
Quicklist combines linked lists with listpacks: a doubly-linked list of listpack nodes with optional LZF compression. This balances list operation efficiency with memory usage, configurable via list-max-listpack-size and list-compress-depth.
Skip List powers sorted sets, providing O(log N) insertion, deletion, and range queries. Combined with a hash table for O(1) member lookups, sorted sets achieve both fast ranking and membership testing.
Stream Encoding
Streams use a radix tree of listpacks indexed by stream IDs (128-bit: milliseconds + sequence). Each listpack stores multiple entries with delta-encoded IDs and field deduplication. Consumer groups track delivery state via pending entry lists (PELs) stored as radix trees.
Loading diagram...
Memory Optimization
Redis automatically converts between encodings as data grows. For example, a set starts as an intset, converts to a listpack when it exceeds set-max-intset-entries, then to a hash table when it exceeds set-max-listpack-entries. This lazy conversion minimizes memory overhead for small collections while maintaining performance for large ones.
Embedded keys in kvobj structures reduce allocation overhead by storing keys directly within the object metadata when possible, particularly beneficial for large keys.
Command Processing Pipeline
Relevant Files
src/server.csrc/networking.csrc/resp_parser.csrc/commands.csrc/commands.h
The command processing pipeline transforms raw network data into executed Redis commands. It consists of three main stages: protocol parsing, command lookup and validation, and command execution.
Protocol Parsing
When a client sends data, it arrives in the query buffer. The pipeline first determines the protocol type:
- RESP (REdis Serialization Protocol): Starts with
*for multibulk arrays - Inline Protocol: Plain text commands separated by spaces
processInputBuffer() routes to the appropriate parser:
processMultibulkBuffer()handles RESP format, parsing the array length, then each bulk string argumentprocessInlineBuffer()handles inline commands, splitting on whitespace
Both parsers populate a pendingCommand structure with argc (argument count) and argv (argument array).
Command Lookup and Validation
Once a complete command is parsed, preprocessCommand() performs early validation:
-
Command Lookup:
lookupCommand()searches the command dictionary using the first argument as the key. It handles both top-level commands and subcommands (e.g.,CONFIG GET). -
Arity Check: Verifies argument count matches the command's arity specification.
-
Reuse Optimization: If the previous command matches, it reuses the cached command pointer to avoid dictionary lookups.
Command Execution
processCommand() performs comprehensive checks before execution:
- Authentication: Verifies client is authenticated (unless command has
CMD_NO_AUTHflag) - ACL Permissions: Checks access control lists
- State Validation: Ensures server state allows the command (not loading, not in readonly mode, etc.)
- Transaction Handling: Queues commands in
MULTIblocks instead of executing immediately
Finally, call() invokes the command's handler function (cmd->proc), which implements the actual logic. The handler receives the client structure and reads arguments from c->argv.
Data Flow Diagram
Loading diagram...
Key Optimizations
- Command Reuse: Caches the last command to avoid repeated dictionary lookups for pipelined commands
- Pending Commands Queue: Buffers multiple commands for efficient batch processing
- Lazy Parsing: Only parses complete commands; incomplete data waits for more input
- Per-Slot Metrics: Tracks network bytes and command statistics per cluster slot
Persistence: RDB & AOF
Relevant Files
src/rdb.c– RDB snapshot save/load implementationsrc/rdb.h– RDB format definitions and opcodessrc/aof.c– AOF append-only file and rewrite logicsrc/rio.c– Redis I/O abstraction layersrc/rio.h– RIO interface for file/buffer/socket I/O
Redis provides two complementary persistence mechanisms: RDB snapshots for point-in-time backups and AOF (Append-Only File) for command-level durability. Both can run independently or together.
RDB Snapshots
RDB creates a binary snapshot of the entire dataset at a specific moment. The format is optimized for fast loading and compression.
Key characteristics:
- Format: Binary with version header (
REDIS0012), metadata, all databases, and CRC64 checksum - Encoding: Supports multiple object types (strings, lists, sets, hashes, sorted sets, streams) with compression (LZF)
- Process: Uses
rdbSave()for synchronous saves orrdbSaveBackground()for background saves via fork - Atomic writes: Writes to temporary file, then renames atomically to final location
- Opcodes: Special markers like
RDB_OPCODE_SELECTDB,RDB_OPCODE_EXPIRETIME_MS,RDB_OPCODE_EOFstructure the file
Loading happens via rdbLoad() or rdbLoadRio(), which validates the magic header, reads all databases, and reconstructs objects in memory.
AOF (Append-Only File)
AOF logs every write command in Redis protocol format, enabling recovery to any point in time.
File structure (managed via manifest):
- BASE: RDB snapshot from last rewrite (optional, can use RDB preamble)
- INCR: Incremental command logs since last rewrite (multiple files possible)
- HISTORY: Previous BASE/INCR files marked for deletion after successful rewrite
Key operations:
feedAppendOnlyFile()buffers commands inserver.aof_bufbefore flushing to diskflushAppendOnlyFile()writes buffered commands with configurable fsync policies (always, everysec, no)rewriteAppendOnlyFileBackground()forks a child to compact the AOF viarewriteAppendOnlyFileRio()
AOF rewrite (triggered by BGREWRITEAOF or automatic thresholds):
- Child process writes new BASE file (RDB or command format)
- Parent opens new INCR file for incoming commands
- On success, manifest updates atomically; old files become HISTORY
- Background job deletes HISTORY files asynchronously
RIO (Redis I/O)
The rio abstraction layer handles I/O to files, buffers, file descriptors, or sockets with:
- Automatic checksumming (CRC64 for RDB)
- Incremental fsync for large writes
- Page cache reclamation to reduce memory overhead
Hybrid Approach
When aof_use_rdb_preamble is enabled, AOF rewrites use RDB format for the BASE file, combining RDB's compression efficiency with AOF's command-level granularity for INCR files.
Loading diagram...
Replication & Clustering
Relevant Files
src/replication.csrc/cluster.csrc/cluster.hsrc/cluster_asm.csrc/sentinel.c
Redis provides three complementary mechanisms for high availability and scalability: replication for data synchronization, clustering for horizontal scaling, and Sentinel for automatic failover.
Replication: Master-Replica Synchronization
Replication enables data synchronization between a master and one or more replicas. The master maintains a replication offset tracking all commands sent, while replicas track their applied offset.
Key Components:
- Replication Backlog: A circular buffer storing recent commands. When a replica reconnects, it can request a partial resync (PSYNC) to receive only missing commands instead of a full RDB snapshot.
- Replication Buffer Blocks: Shared memory blocks referenced by both the backlog and replica clients, reducing memory overhead.
- Full Sync: When partial resync fails, the master generates an RDB snapshot and streams it to the replica, followed by command stream.
- RDB Channel: An optimized dual-channel mechanism where one connection streams the RDB and another buffers commands, improving throughput during full sync.
Synchronization Flow:
Replica connects → PSYNC request → Master checks offset
├─ Offset in backlog → Partial resync (fast)
└─ Offset lost → Full resync (RDB + commands)
Cluster: Distributed Hash Slots
Redis Cluster partitions data across multiple nodes using 16,384 hash slots. Each key is mapped to a slot via CRC16 hashing, with support for hash tags ({tag}) to co-locate related keys.
Architecture:
- Slot Distribution: Each master node owns a range of slots; replicas replicate their master's data.
- Key Routing: Clients compute slot IDs and route requests to the correct node. Redirects (
MOVED,ASK) guide clients to proper nodes. - Atomic Slot Migration (ASM): Moves slots between nodes using dual channels (main + RDB) to minimize downtime and ensure consistency.
- Cluster State: Maintained via gossip protocol; nodes exchange configuration epochs to detect stale information.
Slot Migration States:
Loading diagram...
Sentinel: Automatic Failover Orchestration
Sentinel monitors master-replica topologies and automatically promotes replicas when masters fail. Multiple Sentinels use quorum-based voting to prevent split-brain scenarios.
Failure Detection:
- Subjective Down (S-DOWN): A single Sentinel detects no response within
down-after-period. - Objective Down (O-DOWN): Quorum of Sentinels agree the master is down.
- Failover Trigger: Once O-DOWN is confirmed, Sentinels elect a leader to orchestrate failover.
Failover State Machine:
- Wait Start: Random delay to desynchronize Sentinels
- Select Slave: Choose replica with best priority and replication offset
- Send SLAVEOF NO ONE: Promote selected replica to master
- Wait Promotion: Verify replica became master
- Reconf Slaves: Reconfigure other replicas to replicate from new master
- Update Config: Update Sentinel configuration with new topology
Sentinel also handles configuration propagation via pub/sub and script execution for custom notifications.
Modules & Scripting
Relevant Files
src/module.csrc/redismodule.hsrc/eval.csrc/script.csrc/script_lua.csrc/function_lua.csrc/functions.csrc/functions.h
Redis provides two complementary extension mechanisms: modules for compiled extensions and scripting for dynamic Lua code execution. Both enable custom functionality while maintaining atomicity and isolation.
Module System
Modules are dynamically loaded C libraries that extend Redis with custom commands, data types, and event hooks. The module lifecycle begins with RedisModule_OnLoad(), the entry point called when a module is loaded via the MODULE LOAD command.
Key Components:
- Module Context (
RedisModuleCtx): Opaque handle passed to all module API calls, managing memory, command registration, and server interaction - Module API (
redismodule.h): Comprehensive C API with 200+ functions for memory management, key operations, command creation, and server events - Module Types: Custom data types registered via
RedisModule_CreateDataType(), with callbacks for RDB persistence, AOF rewriting, and memory reporting - Shared APIs: Modules can export functions for other modules via
RedisModule_ExportSharedAPI()
Modules integrate deeply with Redis: they can subscribe to server events (client changes, flushes, replication), implement command filters, handle authentication, and participate in cluster operations.
Lua Scripting
Redis executes Lua scripts atomically via EVAL and EVALSHA commands. Scripts are cached by SHA1 hash to enable efficient re-execution without recompilation.
Script Execution Flow:
- Script is compiled to a Lua function named
f_<SHA1> - Function is stored in the Lua registry and cached in
lctx.lua_scriptsdictionary - On execution, the script receives
KEYSandARGVarrays and can callredis.call()orredis.pcall() - Results are converted from Lua types to Redis protocol and returned to the client
Script Caching & Management:
- LRU eviction via
lua_scripts_lru_listwhen memory limits are exceeded SCRIPT LOADpre-compiles scripts without executionSCRIPT EXISTSchecks cache status;SCRIPT FLUSHclears all scripts- Scripts support shebang headers (
#!lua flags=...) for declaring execution flags
Functions System
Functions extend scripting with persistent, reusable libraries. Unlike ephemeral EVAL scripts, functions are stored in RDB and replicated to replicas.
Function Architecture:
- Libraries: Named collections of functions, loaded via
FUNCTION LOADwith metadata (engine, name, description) - Engines: Pluggable execution environments; currently Lua is the primary engine
- Function Flags: Control execution behavior (
no-writes,allow-stale,allow-oom,no-cluster,allow-cross-slot-keys) - Registration: Functions register themselves via
redis.register_function()during library load
Invocation:
Functions are called via FCALL <name> <numkeys> [keys...] [args...] or FCALL_RO for read-only variants. The execution context (scriptRunCtx) manages isolation, timeout handling, and replication.
Lua API & Sandboxing
Scripts access Redis via redis.call() (raises errors) and redis.pcall() (returns errors). The Lua environment is sandboxed with an allowlist of safe globals and libraries (string, math, table, cjson, bit, struct, os). Dangerous functions like setfenv and getfenv are blocked.
-- Example: Atomic increment with expiry
return redis.call('SET', KEYS[1], redis.call('INCR', KEYS[1]))
Execution Guarantees
Both modules and scripts execute atomically: no other commands or scripts run concurrently. The scriptRunCtx structure tracks execution state, enforcing timeouts (default 5 seconds), preventing writes in read-only mode, and managing cluster key restrictions. Timeout violations trigger script termination and client disconnection.
Memory Management & Eviction
Relevant Files
src/zmalloc.c– Memory allocation tracking and managementsrc/zmalloc.h– Memory allocation API and allocator selectionsrc/evict.c– Eviction policies and LRU/LFU implementationsrc/expire.c– Key expiration and TTL handlingsrc/lazyfree.c– Asynchronous object freeingsrc/defrag.c– Active memory defragmentation
Memory Allocation & Tracking
Redis uses a custom memory allocator wrapper (zmalloc) that tracks all allocations across the system. The wrapper supports multiple underlying allocators: jemalloc (default, recommended), tcmalloc, or libc. Thread-local counters track memory usage per thread to avoid contention, with zmalloc_used_memory() aggregating totals across all threads.
Memory allocation includes safety mechanisms: zmalloc() panics on failure, while ztrymalloc() returns NULL. The allocator also provides usable size information to optimize memory utilization beyond requested sizes.
Eviction Policies & Maxmemory
When memory usage exceeds maxmemory, Redis evicts keys according to the configured policy:
- LRU-based:
volatile-lru(TTL keys only),allkeys-lru(any key) - LFU-based:
volatile-lfu,allkeys-lfu(frequency-based, with decay) - TTL-based:
volatile-ttl(evict keys expiring soonest) - Random:
volatile-random,allkeys-random - No eviction: Reject writes when over limit
The eviction pool maintains 16 candidate keys sampled from the dataset. During eviction, Redis samples maxmemory-samples keys (default 5) and selects the best candidate from the pool. This approximates true LRU/LFU in constant memory.
LFU Implementation
LFU uses 24 bits per object: 16 bits for last access time (in minutes) and 8 bits for a logarithmic frequency counter. The counter saturates at 255 and decays over time based on lfu-decay-time. New keys start at LFU_INIT_VAL to avoid immediate eviction. Logarithmic increment (LFULogIncr) makes frequent keys harder to increment, creating a natural frequency distribution.
Expiration & Active Cleanup
Keys with TTL are stored in a separate expires dictionary. Redis uses two expiration mechanisms:
- Passive expiration: Keys expire on access
- Active expiration cycle: Runs at
server.hzfrequency (default 10 Hz), sampling keys and removing expired ones
The active cycle adapts effort based on expiration density. If many keys are stale, it increases sampling; if few expire, it reduces work to avoid CPU waste.
Lazy Freeing
Large objects are freed asynchronously in a background thread via lazyfree.c. This prevents blocking the main thread during eviction or deletion of large structures (hashes, lists, sets). The background thread decrements reference counts and updates memory statistics.
Active Defragmentation
When using jemalloc with fragmentation hints, Redis can actively defragment memory. The defrag process scans the keyspace in stages, asking jemalloc if moving allocations would reduce fragmentation. Defragmentation runs in bounded time windows to maintain responsiveness. It's controlled by activedefrag config and fragmentation thresholds.
Memory Overhead Exclusions
Replication buffers and AOF buffers are excluded from eviction calculations to prevent feedback loops: evicting keys generates DEL commands that fill buffers, triggering more evictions. Client connection buffers can also be evicted via maxmemory-clients to prevent client memory from consuming all available memory.
Loading diagram...