Inside Redis: Unveiling Its Core Architecture and Data Storage Mechanics
This article provides a detailed walkthrough of Redis's internal architecture, covering the client module, event‑driven networking, command processing, memory management, persistence strategies, high‑availability features, and the low‑level data structures such as redisServer, redisDb, dict, dictEntry, and redisObject that power its fast key‑value storage.
Redis consists of several core modules: a C‑language client for sending commands and performing performance tests; a network layer built on an I/O multiplexing‑based event‑driven library ( ae) that abstracts epoll, select, kqueue, and evport; a command parsing and execution layer handling commands like SET, GET, DEL; memory allocation and reclamation; persistence via RDB snapshots and AOF logs; high‑availability components (replication, sentinel, cluster); and monitoring tools for memory usage, latency, and big‑key statistics.
To start Redis, run the redis-server executable (e.g., ./redis-server ../redis.conf). Each running instance is represented by a redisServer structure defined in server.h. Key fields include process ID, main thread ID, configuration file path, a pointer to the database array ( redisDb *db), command table, event loop ( aeEventLoop *el), sentinel mode flag, listening port, client lists, and the currently executing client.
Data Storage Principle
The redisDb *db pointer references an array of redisDb structures (default size 16). Each redisDb contains:
typedef struct redisDb {
dict *dict;
dict *expires;
dict *blocking_keys;
dict *ready_keys;
dict *watched_keys;
int id;
long long avg_ttl;
unsigned long expires_cursor;
list *defrag_later;
clusterSlotToKeyMapping *slots_to_keys;
} redisDb;dict stores the actual key‑value pairs, while expires holds expiration timestamps in a separate hash table to save memory for keys without TTL.
MySQL: “Why store them separately?” Because only a subset of keys have expiration times, and separating the tables reduces overall memory consumption despite requiring two lookups.
blocking_keys and ready_keys support blocking commands such as BLPOP. When a client blocks on a key, the key is placed in blocking_keys with the waiting client as the value; once a matching PUSH arrives, the key moves to ready_keys for immediate processing.
watched_keys implements the WATCH command by tracking keys monitored for changes.
Other fields include id (database identifier), avg_ttl (average time‑to‑live), expires_cursor (counts expiration loop iterations), defrag_later (keys scheduled for incremental defragmentation), and slots_to_keys (used only in Cluster mode to map hash slots to keys).
dict Structure
The dict type implements a hash table similar to Java’s HashMap. Its definition in dict.h includes:
struct dict {
dictType *type;
dictEntry **ht_table[2];
unsigned long ht_used[2];
long rehashidx;
int16_t pauserehash;
signed char ht_size_exp[2];
}; typestores function pointers for hashing, key/value duplication, and destruction. ht_table[0] holds the primary hash table; ht_table[1] is used during incremental rehashing. rehashidx indicates the current position in the rehash process (‑1 means no rehash). pauserehash signals whether rehashing is paused. ht_used[2] counts entries in each table, influencing collision probability. ht_size_exp[2] records the exponent of each table’s size (i.e., number of buckets).
Only ht_table[0] is active under normal operation; when the table grows, ht_table[1] is allocated and entries are gradually moved.
dictEntry
Each bucket stores a dictEntry:
typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next;
void *metadata[];
} dictEntry; *keypoints to an SDS string representing the key. v is a union holding the value; numeric types are stored directly to avoid extra allocations, while other types use val to point to a redisObject. *next links entries that share the same hash bucket, implementing chaining for collision resolution.
Thus, hash buckets contain pointers to redisObject instances rather than the raw values, enabling support for multiple data types.
redisObject
The actual value is wrapped in a redisObject defined in server.h:
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:LRU_BITS;
int refcount;
void *ptr;
} robj;type identifies the data type (string, list, set, hash, sorted set, etc.).
encoding specifies the internal representation used for that type.
lru stores LRU or LFU metadata for eviction policies.
refcount tracks how many structures reference the object; when it reaches zero, the object can be freed.
ptr points to the concrete data structure holding the value.
The relationship among redisDb, dict, dictEntry, and redisObject is illustrated below:
During normal operation only ht_table[0] is used. When the table reaches capacity, a larger ht_table[1] is allocated and entries are migrated incrementally (gradual rehash). Once migration finishes, ht_table[0] is switched to the new table and ht_table[1] is cleared.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
ITPUB
Official ITPUB account sharing technical insights, community news, and exciting events.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
