Redis is an in-memory data structure server. Clients speak RESP over TCP; the process stores keys in logical databases and serves commands from a single-threaded event loop that owns the keyspace. Logical types (string, list, set, …) are separate from physical encodings chosen for memory and CPU cost.

This post covers general concepts, process architecture, and the type / encoding map as implemented in current Redis source (server.h, object.h, server.c).

Related: Build Redis from source.


1. Generals

1.1 What Redis is

IdeaMeaning
Data structure serverValues are typed structures (lists, sets, streams), not opaque blobs only
In-memory primaryHot path is RAM; disk is for durability and restart
Key → valueEvery entry is addressed by a string key inside a selected DB
Logical DBSELECT switches among server.db[id] instances (default 0)
Single-threaded commandsOne thread runs processCommand / call against the keyspace (I/O may use extra threads)

Redis is not a relational engine: there is no SQL planner. Commands are registered procedures (redisCommand) that mutate or read robj values under keys.

1.2 Client protocol (RESP)

Clients send arrays of bulk strings (command name + args). The server parses into client->argv / argc, looks up the command, then executes it. Replies are RESP integers, bulk strings, arrays, errors, or push messages (for Pub/Sub and some tracking features).

1.3 Persistence and durability (overview)

MechanismRole
RDBPoint-in-time binary snapshot (rdb.c); cheap restart image
AOFAppend-only log of write commands (aof.c); finer durability
BIO / forkBackground save and rewrite avoid blocking the main loop for long

Replication and Cluster build on the same command path: writes are propagated to replicas or to slot owners after local execution.

1.4 Concurrency model

  • Main thread: event loop (aeMain), command execution, expire, eviction hooks in beforeSleep.
  • Optional I/O threads: read queries / write replies off the main thread; still hand off to main for command execution.
  • Fork children: RDB/AOF rewrite; copy-on-write shares pages with the parent until dirty.

Blocking commands (BLPOP, XREAD BLOCK, …) park the client and resume when keys become ready—still without a second command-executing thread on the keyspace.


2. Architecture

2.1 Layered process view

Redis process architecture

2.2 Event loop and request path

Redis drives I/O with ae (ae.c / ae.h): file events (sockets) and time events (cron). Each loop iteration can run beforeSleep before waiting on the OS multiplexer (epoll/kqueue/select).

Typical path for a simple command:

sequenceDiagram
    participant C as Client
    participant N as Networking
    participant AE as ae event loop
    participant PC as processCommand
    participant CALL as call
    participant DB as redisDb / kvstore

    C->>N: RESP query buffer
    N->>AE: readable event
    AE->>PC: argv ready
    PC->>PC: ACL, arity, key prep
    PC->>CALL: cmd->proc
    CALL->>DB: read / mutate kvobj
    CALL->>N: reply + propagate
    N->>C: RESP response

Source anchors:

  • aeMain(server.el) — main loop entry (server.c)
  • beforeSleep — pending writes, AOF flush helpers, expire-related work before polling
  • processCommand(client *c) — validation and dispatch
  • call(client *c, int flags) — invoke c->cmd->proc, stats, propagation
// server.c — conceptual shape
int processCommand(client *c) {
    /* module filters, ACL, arity, cluster redirection, … */
    call(c, flags);   /* or queue / block instead */
    return C_OK;
}

2.3 Keyspace: redisDb and kvstore

Each logical database is a redisDb:

// server.h
typedef struct redisDb {
    kvstore *keys;       /* keyspace */
    kvstore *expires;    /* keys with TTL */
    estore *subexpires;  /* field-level TTL (e.g. hash) */
    dict *blocking_keys;
    dict *watched_keys;
    /* … */
    int id;
} redisDb;

kvstore is a slot-aware collection of hash tables (important for Cluster). Lookups go through helpers such as dbFind / dbAdd rather than a single global dict per DB as in older Redis.

Keys with TTL appear in expires; deletion is lazy on access and active during cron/beforeSleep cycles (expire.c).

2.4 Object model: robj / kvobj

Every value is a redisObject (object.h):

struct redisObject {
    unsigned type:4;       /* OBJ_STRING, OBJ_LIST, … */
    unsigned encoding:4;   /* OBJ_ENCODING_*, physical layout */
    unsigned refcount : /* … */;
    unsigned iskvobj : 1;
    unsigned metabits : 8; /* when iskvobj: expiry / metadata */
    unsigned lru:24;       /* LRU or LFU bits */
    void *ptr;             /* SDS, dict, quicklist, stream, … */
};
  • type: what the API promises (commands check type).
  • encoding: how ptr is laid out; may change as the value grows (hashTypeConvert, setTypeConvert, …).
  • kvobj: same struct used as a key–value unit that can embed the key (and optional expiry metadata) beside the value—see the header comment in object.h.

Shared integers and common reply objects use elevated refcount so they are never freed.

2.5 Persistence, replication, cluster (placement)

@startuml
!option handwritten true
skinparam class {
    BackgroundColor White
    BorderColor Black
    ArrowColor Black
}

class redisServer {
  +el : aeEventLoop
  +db : redisDb*
  +aof_state
  +master / replicas
}

class redisDb {
  +keys : kvstore
  +expires : kvstore
}

class redisObject {
  +type
  +encoding
  +ptr
}

class redisCommand {
  +name
  +proc
  +flags
}

redisServer "1" *-- "N" redisDb
redisDb "1" --> "*" redisObject : keys hold
redisServer --> redisCommand : command table
redisCommand ..> redisDb : proc mutates
@enduml
SubsystemFiles (entry)Notes
Networkingnetworking.cAccept, query parse, reply buffers
Commandst_*.c, db.c, multi.cPer-type and generic key ops
RDB / AOFrdb.c, aof.cSnapshot and rewrite
Replicationreplication.cPSYNC, backlog, replica feed
Clustercluster.cHash slots, redirection
Modulesmodule.c, redismodule.hOBJ_MODULE custom types

3. Data types and encodings

Logical types are defined in server.h (OBJ_STRINGOBJ_STREAM, plus module/array). Encodings are defined in object.h. Type is stable for a key’s API; encoding is an implementation detail that can convert.

3.1 Type ↔ encoding map

TypeTypical encodingsPayload (ptr)Convert when
StringINT, EMBSTR, RAWinteger / embedded SDS / heap SDSlength or non-integer content
ListLISTPACK, QUICKLISTsingle listpack or linked listpackslength / node size thresholds
HashLISTPACK, LISTPACK_EX, HTcompact fields or dictfield count / size limits
SetINTSET, LISTPACK, HTintegers / compact / hashtablemember count or non-int members
ZSetLISTPACK, SKIPLIST (+ dict)compact pairs or skiplist+dictsize thresholds
StreamSTREAMradix tree (rax) of listpacksN/A (stream-native)
Modulemodule-definedmoduleValuemodule owns layout

Legacy encodings (ZIPLIST, LINKEDLIST, ZIPMAP) remain as constants for RDB compatibility but are not created for new data.

3.2 String

Commands: GET / SET / INCR / APPEND / bitops (t_string.c, bitops.c).

EncodingWhen
OBJ_ENCODING_INTValue fits a long; no SDS allocation
OBJ_ENCODING_EMBSTRShort string; object header and SDS in one allocation
OBJ_ENCODING_RAWLonger or mutable SDS

OBJECT ENCODING key reports the current encoding.

3.3 List

Commands: LPUSH / RPUSH / LPOP / LRANGE / blocking variants (t_list.c).

EncodingStructure
LISTPACKOne contiguous listpack for small lists
QUICKLISTLinked list of listpack nodes; optional compression of interior nodes

3.4 Hash

Commands: HSET / HGET / HDEL / field TTL variants (t_hash.c).

EncodingStructure
LISTPACK / LISTPACK_EXAlternating field/value (extended form carries metadata such as field expiry)
HTdict of field → value SDS

3.5 Set

Commands: SADD / SREM / SISMEMBER / set algebra (t_set.c).

EncodingStructure
INTSETCompact sorted integers
LISTPACKCompact non-int-friendly small sets
HTHash table of members

3.6 Sorted set (ZSet)

Commands: ZADD / ZRANGE / ZRANK (t_zset.c).

EncodingStructure
LISTPACKMember/score pairs in one listpack
SKIPLISTSkiplist ordered by score + dict for O(1) member lookup

3.7 Stream

Commands: XADD / XREAD / consumer groups (t_stream.c).

Encoding is OBJ_ENCODING_STREAM: a radix tree of listpack nodes holding entries, plus consumer-group state. Streams are append-oriented logs with IDs, not random-access maps.

3.8 Inspecting types at runtime

TYPE mykey
OBJECT ENCODING mykey
OBJECT FREQ mykey          # when LFU
MEMORY USAGE mykey

Conversion is automatic when size thresholds in redis.conf are crossed (e.g. hash-max-listpack-entries, set-max-intset-entries, zset-max-listpack-entries).


4. Design consequences

  1. API type ≠ memory layout — capacity planning and latency cliffs often come from encoding conversions, not from the logical type name.
  2. Single-threaded mutation — no per-key locks on the main command path; long commands or large KEYS scans stall everyone.
  3. Expire is opportunistic — TTL keys are not always deleted at the exact millisecond; combine lazy + active expire.
  4. kvstore / slots — Cluster and keyspace iteration are designed around slot-sharded dictionaries rather than one flat table.
  5. Modules extend typesOBJ_MODULE plugs custom serialization into RDB/AOF via module type callbacks.

5. Summary

LayerCore types / entry points
Event & I/OaeEventLoop, beforeSleep, networking.c
DispatchprocessCommandcallredisCommand.proc
KeyspaceredisDb, kvstore, expires
Valuesrobj / kvobj: type + encoding + ptr
DurabilityRDB + AOF; replication/cluster reuse command propagation

Redis’s public surface is a set of data-type commands; its internals are an event-driven process whose keyspace stores reference-counted objects that switch encodings as data grows. Understanding that split is enough to reason about memory, latency, and why OBJECT ENCODING matters in production.

Build and run notes: Redis Build from source.