If you have ever cached an LLM response, rate-limited an API, or served precomputed features to a model at inference time, there is a very good chance Redis was sitting somewhere in the request path. Because the dataset lives in memory and is served by hand-tuned C data structures, reads and writes routinely land in the sub-millisecond range — exactly the budget a machine-learning service has left over after the model takes its share of the latency. That combination has made Redis the default hot layer of the AI stack: embeddings lookups, feature stores, session state, token-bucket counters, and leaderboard-style rankings all fit its model of “keep it in RAM, make every operation O(1) or O(log N)”.
The redis/redis repository on GitHub is the home of the open-source Redis codebase: a compact, deeply readable C project whose src/ directory contains essentially the entire server. Everything lives in one flat folder — the event loop, the client networking layer, every data type command, the snapshot and log writers, the replication engine, and the cluster bus protocol. There is almost nothing between you and the metal: just files with names like networking.c and t_zset.c that do exactly what they say.
This post is a guided tour of that source. The default development branch of the repository is unstable (the maintainers’ rolling tip; releases are cut from version branches), and every symbol and line number below was verified against that branch before publication. Two diagrams generated from the repository tree accompany the tour: an overview of the runtime architecture, and a denser detail map of the same code. One interesting wrinkle for 2026: the tree now bundles a modules/vector-sets directory — an HNSW-based similarity-search structure (vset.c, hnsw.c) that plugs the classic Redis command model into the vector-search workflow that tools like pgvector serve inside Postgres.
The runtime at a glance: a single-threaded event loop reads client commands, dispatches them to the data type implementations, and fans write commands out to the persistence layers and to replicas. Generated from the redis/redis tree, branch unstable.
Reading the overview from left to right:
- The process starts conventionally.
int main, L8369 parses configuration, initializes the databases and shared objects, and then hands control to the event loop. - The loop itself is
aeMain, L497, a thin wrapper that repeatedly callsaeProcessEvents, L365, which polls sockets and timers through pluggable backends — the tree shipsae_epoll.c,ae_kqueue.c,ae_select.c, andae_evport.c. - On every readable client socket the backend invokes
readQueryFromClient, L4004, which appends bytes to the client’s query buffer, andprocessInputBuffer, L3801, which parses the RESP framing into an argv array. - Parsing hands off to
processCommand, L4672, the dispatch gate that resolves the command table entry, checks authentication, arity, and key validity, then executes the command. - String operations run in
setCommand, L430 andgetCommand, L470; hashes inhsetCommand, L4257; sorted sets inzaddGenericCommand, L2250; lists inlpushCommand, L529. - Every write that succeeds is propagated by
propagateNow, L3808 into two directions at once: the append-only log (viafeedAppendOnlyFile, L1671) and the replication stream (viareplicationFeedSlaves, L656). - Snapshots are taken by
rdbSaveBackground, L2352, which forks a child process so the parent never stops serving traffic.
The detail map: the event loop internals, the RESP parsing path, ten command entry points across four data types, the RDB/AOF persistence machinery, the SYNC/PSYNC state machine, and the cluster gossip engine. Same tree, same branch, 34 nodes.
One Thread, One Loop, Zero Surprises
The most important architectural fact about Redis is what is not there: there is no lock table, no per-request thread, no connection worker pool. The main thread runs the event loop and does all client work; bioProcessBackgroundJobs, L261 is the exception that proves the rule — a small pool of worker threads for background jobs that must never block the loop: closing file descriptors, fsyncing the AOF, and lazily freeing large values. The job classes are created by bioCreateCloseJob, bioCreateFsyncJob, and bioCreateLazyFreeJob further up the file, and src/bio.c is only a few hundred lines long.
Between loop iterations, two hooks give the server its housekeeping rhythm. serverCron, L1581 runs on a timer and handles key expiration sampling, RDB save-point checks, and the cluster tick; beforeSleep, L1966 runs right before the loop blocks on I/O and does the latency-critical flushes — calling flushAppendOnlyFile and pushing pending replies out through handleClientsWithPendingWrites (both calls sit in its body, lines 1981–1982 and 2123). This split — fast bookkeeping when the loop is about to sleep — is a pattern worth stealing for any event-driven system you build.
The payoff of the single-threaded design is that every command is atomic without any locking protocol, which is precisely why INCR (incrCommand, L936) works as a distributed counter or rate limiter straight out of the box. The cost is equally clear: any command that blocks the loop blocks everyone, which is why blocking-list operations and slow disk work are the two places the codebase works hardest to defer.
Five Data Types, One Dispatch Table
Redis’s data type layer is a study in the encoding pattern: each logical type has several physical representations, chosen automatically by size. A hash starts life as a compact listpack and converts to a full dict when it grows; a sorted set flips from listpack to a skiplist-plus-dict pair at a threshold. The command implementations carry this logic inside themselves — genericHgetallCommand, L5601 iterates whichever encoding it finds, and zrangeGenericCommand, L4529 serves ZRANGE, ZREVRANGE, and their variants from one generic body. For a rank lookup, zrankGenericCommand, L4750 walks the skiplist’s width spans rather than counting elements one by one.
Lists ride on quicklist.c — a linked list of listpack nodes — which gives lpushCommand, L529 cheap head insertion while keeping memory dense. The blocking variants are the fun part: blpopCommand, L1375 parks the client on a “blocked clients” registry keyed by the awaited key, and a later push unblocks the waiter. That primitive is the backbone of countless job queues, including the worker hand-off schemes people build around batch inference services.
This dispatch-plus-encoding architecture is exactly what made the vector-sets addition feel native: a new type arrives with its own commands and its own encoding strategy (the HNSW graph in modules/vector-sets/hnsw.c), reusing the same command-table plumbing the string and hash commands have used since 2009. The similarity-search commands live in the bundled module (modules/vector-sets/vset.c), and the tree carries a full Python test suite for them under modules/vector-sets/tests/.
From Install to Your First Model
The README’s build path is deliberately boring, and that is a feature. Clone the repository, then:
make -j "$(nproc)"
./src/redis-server redis.conf
make (equivalently make build / make all) builds the core server plus whatever is cloned under modules/. The binary lands in src/, and redis-cli is built alongside it — the README’s “Using Redis with redis-cli” section walks through connecting and issuing your first commands. If you only want the core data structures without the bundled modules’ toolchain (the modules bring heavier dependencies like LLVM, Rust, and CMake, installable via make bootstrap), the README notes the core-only alternative: build with make build redis and run ./src/redis-server with no config file.
From there the AI-flavored experiments need nothing but the primitives you have now met in the source. Cache model completions with SET/GET and a TTL; gate an endpoint with a token bucket built on INCR and expiry; index precomputed features in hashes and fetch a whole record with HGETALL; rank candidates in a sorted set and read the top slice with ZRANGE. If you installed the bundled modules, vector sets give you similarity search over embeddings without leaving the server — a lighter-weight sibling to the dedicated vector databases we covered in the pgvector tour. A common production pairing is Redis in front as the hot layer with a bulk store behind it — object storage like MinIO for checkpoints and raw corpora, or a columnar engine like ClickHouse for analytics — with Redis absorbing the per-request traffic that would otherwise hammer them.
One practical note before you ship any of this: source-built Redis is fast, but it is not a persistence miracle. Tune the AOF fsync policy to match your durability tolerance, and read the next section before betting correctness on snapshot timing. If your orchestration story needs cluster mode, size your key space with hash tags from day one.
Persistence: Two Insurance Policies
Redis offers two complementary durability mechanisms in clearly separate files. RDB snapshots (rdb.c) are point-in-time binaries: rdbSaveBackground, L2352 forks a child that serializes the dataset via the rio abstraction (rdbSaveRio at L2156) while the parent keeps serving; at startup, rdbLoad, L5396 restores the full state before the server accepts traffic. Forking is the trick — the child sees a frozen world thanks to copy-on-write, and the parent’s pause is limited to the fork itself.
AOF (aof.c) is the opposite trade: feedAppendOnlyFile, L1671 appends every propagated write to a log buffer, and flushAppendOnlyFile, L1409 — invoked from beforeSleep each loop cycle — writes and fsyncs it according to policy, sometimes deferring the fsync to the background threads through a bio job (the bioCreateFsyncJob call sits inside this function’s everysec path). When the log grows unwieldy, rewriteAppendOnlyFileBackground, L3291 forks a child that rebuilds the log from the current dataset, the same fork-and-freeze pattern as RDB. In practice most workloads run both: RDB for compact backups, AOF for bounded data loss on crash.
Replication and Cluster: Scaling Reads and Writes
Replication is a replica-pull protocol. A follower connects and issues SYNC or PSYNC, handled in syncCommand, L1213. If the follower’s offset is still inside the master’s replication backlog, masterTryPartialResynchronization, L1011 resumes the stream without a full copy; otherwise replicationSetupSlaveForFullResync, L951 schedules a BGSAVE handoff and ships the snapshot. From then on, replicationFeedSlaves, L656 appends every write to each replica’s output buffer — the same propagateNow fan-out you saw feeding the AOF.
Cluster mode shards the key space across nodes with the well-known 16384 hash slots: keys map to slots through CRC16 masked to 14 bits (see the slot helper at the top of src/cluster.c), and each node owns a slice of slots. The gossip engine lives in cluster_legacy.c: clusterCron, L4878 paces the protocol, clusterSendPing, L3827 pushes gossip messages over the bus, clusterProcessPacket, L2829 decodes incoming MEET/PING/PONG/FAIL traffic, and clusterUpdateSlotsConfigWith, L2389 remaps slot ownership when the topology changes. The common pieces — slot computation, DUMP/RESTORE, MIGRATE — sit in cluster.c itself, which the header describes as the parts shared between clustering implementations. This is the machinery for when a single hot cache outgrows one machine; for most AI services it is overkill until it very suddenly is not.
Honest Limits
- RAM is the budget. Every byte you cache lives in memory, and the copy-on-write fork during BGSAVE or AOF rewrite can briefly double the footprint of a large dataset. Plan headroom, or use the lazy-free jobs to soften deletions.
- Single-threaded means fairness matters. One expensive command — a huge key range, a runaway sort — delays every other client. The blocking primitives help queues, not compute-heavy scans.
- Durability is a dial, not a guarantee. AOF
everysectrades one second of writes on crash; RDB snapshots lose everything since the last save. The source makes the trade-offs legible, but you still choose them. - Cluster mode constrains your data model. Multi-key operations only work when keys share a slot (hash tags), and resharding moves real data. The 16384-slot design is elegant but opinionated.
- The
unstablebranch moves. Line numbers here were verified against the tree at the time of writing; expect drift — the symbol names are far more durable than the line numbers. - Vector sets are young. The bundled
modules/vector-setsmodule is new relative to the core types; treat it as promising but validate persistence and scaling for your workload before committing.
If this tour changes how you read Redis — from a black-box cache into a legible event-loop program with five data types bolted onto one dispatch table — it did its job. Pair it with the right bulk stores, and it remains the cheapest latency you will ever buy. Enjoyed this post? Never miss out on future posts by following us