redis/redis
github.com/redis/redis ↗ · 1866 files · 20.8 MB
This document serves as both a quick start guide to Redis and a detailed resource for building it from source.
Overview
This is the Redis Open Source server (`src/version.h` reports 8.9.241, branch `unstable`) — a single-process, event-loop-driven in-memory data structure server written in C99/gnu11, plus its CLI, benchmark, sentinel, and RDB/AOF checking tools. Beyond upstream Redis it carries a substantial in-tree feature layer: a new `OBJ_ARRAY` sparse-array type (`src/t_array.c`, `src/sparsearray.c`), key eviction protection (`src/bless.c`), CuckooHeavyKeeper hot-key tracking (`src/chk.c`, `src/hotkeys.c`), atomic cluster slot migration (`src/cluster_asm.c`), zstd replication/client compression (`src/client_comp.c`), and multi-threaded IO (`src/iothread.c`). It also ships a bespoke top-level Makefile + `scripts/` orchestration layer that clones, builds, deploys, and tests four external Redis modules (RediSearch, RedisJSON, RedisBloom, RedisTimeSeries) pinned by `modules/modules.yaml`, which upstream Redis does not have.
System map
The runtime is one `redis-server` binary whose `main()` in `src/server.c` initializes config, ACL, the module system and connection types, then hands control to the `ae` event loop (`aeMain`). The same binary multiplexes into sentinel, `redis-check-rdb` and `redis-check-aof` modes by argv[0] inspection. Storage is entirely in-process (`kvstore`/`dict`/`listpack`/`quicklist`/`rax`/`ebuckets`), persisted by RDB snapshot and/or AOF manifest, and replicated over a custom PSYNC protocol. Build is GNU Make: `src/Makefile` compiles the core and vendored `deps/`, while the top-level `Makefile` delegates to `scripts/*.sh` for the module-bundle workflow. Deployment shape is a bare binary + `redis.conf` (or the generated `redis-full.conf`), with an Ubuntu 24.04 build image in `docker/Dockerfile.noble`.
- `main()` in src/server.c is the single entrypoint for redis-server, redis-sentinel, redis-check-rdb and redis-check-aof: it calls `checkForSentinelMode()` on argv, then dispatches to `redis_check_rdb_main()` / `redis_check_aof_main()` when `strstr(exec_name, "redis-check-rdb"/"redis-check-aof")` matches, before any config parsing.
- Startup order in `main()` is fixed and dependency-driven: `initServerConfig()` → `ACLInit()` (commented 'must be initialized ASAP because the basic networking code and client creation depends on it') → `moduleInitModulesSystem()` → `connTypeInitialize()` → `keyMetaInit()`.
- The event loop is created in `initServer()` via `aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR)`, and per-DB storage is allocated as three parallel structures — `kvstoreCreate()` for `db->keys`, `db->expires`, plus `blessedKvstoreCreate()` for `db->blessed_keys` and `estoreCreate()` for `db->subexpires`.
- `main()` ends with `aeMain(server.el)` after `redisSetCpuAffinity(server.server_cpulist)` and `setOOMScoreAdj(-1)`; everything after that point is event-loop driven.
- The core links 130+ objects listed in `REDIS_SERVER_OBJ` in src/Makefile, including the non-upstream modules `bless.o t_array.o sparsearray.o chk.o hotkeys.o gcra.o vector.o fwtree.o estore.o client_comp.o cluster_asm.o iothread.o keymeta.o`.
- Vendored third-party code lives in `deps/` and is built as separate targets: `DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram fpconv xxhash tre` — note `tre` (regex) is a dependency of the new array type, which includes `../deps/tre/local_includes/tre.h`.
- The top-level Makefile is not upstream's: it includes `modules/manifest.mk`, defines a `GOALS_WITH_ARGS` positional-argument capture mechanism, and routes `build`/`clean`/`test`/`deploy`/`bootstrap`/`run`/`tarball` to `scripts/*.sh` rather than recursing into `src/`.
- `modules/modules.yaml` is the single source of truth pinning four external modules to immutable refs: redisbloom v8.11.81, redisearch v8.10.1 (with `build_env: LTO=1 REDISEARCH_GENERATE_HEADERS=0 INLINE_LSE_ATOMICS=0`), redisjson v8.10.0, redistimeseries v8.10.0.
- Test entrypoints are four shell wrappers that locate tclsh 8.5–9.0 and invoke `tests/test_helper.tcl`: `./runtest`, `./runtest-moduleapi` (which first runs `$MAKE -C tests/modules` then names 48 `--single unit/moduleapi/*` suites), `./runtest-cluster`, `./runtest-sentinel`.
- `make -C src test` depends on building redis-server, redis-check-aof, redis-cli, redis-benchmark and `module_tests` before shelling out to `(cd ..; ./runtest)`; `test-modules`, `test-sentinel`, `test-cluster` are separate targets.
- External integrations are network protocols rather than SDKs: the cluster bus (`src/cluster_legacy.c`), the PSYNC replication protocol (`src/replication.c`), TLS via OpenSSL gated on `BUILD_TLS=yes|module` (`src/tls.c`), and optional zstd compression gated on `-DUSE_COMPRESSION=1` detected via pkg-config.
- The deployment artifact set is defined by `scripts/deploy.sh` / `scripts/uninstall.sh`: core binaries and symlinks into `$(PREFIX)/bin` (default `/usr/local`) and module `.so` files into `$(PREFIX)/lib/redis/modules`, with `loadmodule` paths rewritten into a generated `redis-full.conf`.
Feature catalog
User-facing surface is the RESP command set — 464 JSON command specs under `src/commands/` compiled into the 13k-line `src/commands.def`. Alongside the classic types (string/list/set/zset/hash/stream/HLL/geo/bitmap) this tree adds a first-class array type (AR* commands), key-blessing (BLESS), hot-key tracking (HOTKEYS), INCREX, hash-field expiration (HEXPIRE family), and a compiled-out GCRA rate limiter. Administrative surface spans ACL, CLIENT, CLUSTER (including atomic slot migration), CONFIG, FUNCTION, DEBUG, and the Sentinel command set. Client-side tooling ships as `redis-cli` (with a full cluster manager and ~60 flags), `redis-benchmark`, and the two check tools.
- 464 command specification JSON files under `src/commands/` are the declarative source of the command table; `utils/generate-command-code.py` compiles them into `src/commands.def` (13,236 lines) via the `$(COMMANDS_DEF_FILENAME).def` Make rule.
- A new `OBJ_ARRAY` (type 7) sparse-array data type is exposed via 18 commands — ARSET, ARGET, ARDEL, ARDELRANGE, ARGETRANGE, ARGREP, ARINFO, ARINSERT, ARLASTITEMS, ARLEN, ARMGET, ARMSET, ARNEXT, AROP, ARRING, ARSCAN, ARSEEK, ARCOUNT — implemented in `src/t_array.c` over `src/sparsearray.c`.
- BLESS marks keys as never-evictable: the NO-EVICT bit lives in the kvBits byte after each kvobj, persists through RDB and AOF, and is exposed as BLESS SET/GET/CLEAR/SCAN plus an `blessed_keys` INFO counter backed by a per-DB `db->blessed_keys` index.
- HOTKEYS START/STOP/GET/RESET/HELP expose a CuckooHeavyKeeper top-K sketch (`src/chk.c`, based on the Ngo & Papatriantafilou paper plus a min-heap borrowed from RedisBloom's TopK) that can be scoped to specific cluster slot ranges via a `slotRangeArray`.
- INCREX is a new string command returning a two-element reply (`{value, applied}`) that creates a missing key at 0 before incrementing, with an optional increment argument.
- Hash-field expiration ships as eight commands — HEXPIRE, HEXPIREAT, HPEXPIRE, HPEXPIREAT, HEXPIRETIME, HPEXPIRETIME, HGETEX, HGETDEL — backed by the `ebuckets` radix-of-buckets expiry structure and the per-DB `estore` subexpires store.
- A GCRA (Generic Cell Rate Algorithm) leaky-bucket rate limiter exists as `src/gcra.c` with an `OBJ_GCRA` object type and a 4-arg `GCRA key max_burst tokens_per_period period` command, ported from redis-cell — but it is compiled out by default behind `#ifdef ENABLE_GCRA`.
- Atomic slot migration is a user-facing cluster feature driven by `CLUSTER MIGRATION IMPORT <slots>` run on the destination node, handled by `clusterMigrationCommand()`, with an eight-step protocol documented in the file header.
- Lua scripting is offered in two forms: ephemeral EVAL/EVALSHA/SCRIPT via `evalGenericCommand()` and `luaCreateFunction()` in `src/eval.c`, and persistent FUNCTION libraries via a pluggable engine registry in `src/functions.c` whose only registered engine is `LUA_ENGINE_NAME` from `src/function_lua.c`.
- `redis-cli` is a large multi-mode tool (11,561 lines) exposing analysis modes `--bigkeys --memkeys --hotkeys --keystats-samples --scan --latency --latency-dist --latency-history --intrinsic-latency --lru-test --rdb --functions-rdb --pipe --replica --eval --ldb`, RESP3/JSON output modes, and TLS flags (`--tls --cert --key --cacert --cacertdir --insecure`).
- `redis-cli --cluster` embeds a full cluster manager dispatch table of `clusterManagerCommandProc` functions — create, add-node, del-node, info, check, fix, reshard, rebalance — with ~25 `--cluster-*` option flags.
- Sentinel is a distinct operating mode of the same binary: `initSentinelConfig()` + `initSentinel()` run before config parsing so the config file populates monitored-master state, and `sentinelIsRunning()` replaces the normal ready-to-serve path in `main()`.
Data flow
Input arrives on a connection abstraction (`connection.h` over socket/unix/TLS), is read into a per-client or thread-reusable query buffer by `readQueryFromClient()`, parsed into `pendingCommand` structs by `processInlineBuffer()`/`processMultibulkBuffer()`, dispatched by `processCommand()` and executed by `call()`. Command handlers mutate the authoritative state owner — `server.db[j]` kvstores — and any side effects are queued through `alsoPropagate()`. At the end of the execution unit `propagateNow()` fans out to exactly three sinks: the AOF buffer, the replica stream, and the atomic-slot-migration client. Replies accumulate in per-client output buffers and are flushed in `beforeSleep()` by `handleClientsWithPendingWrites()`, strictly after `flushAppendOnlyFile()` so `appendfsync=always` is honoured before acknowledgement.
- The input boundary is `readQueryFromClient(connection *conn)`, which recovers the client via `connGetPrivateData(conn)` and reads `PROTO_IOBUF_LEN` bytes, except when a multibulk argument exceeds `PROTO_MBULK_BIG_ARG`, in which case it sizes the read to exactly the remaining bulk length to enable zero-copy object creation.
- Query buffers are pooled per IO thread: a client with no private `querybuf` borrows `thread_reusable_qb` and sets `CLIENT_IO_REUSABLE_QUERYBUFFER`; if `thread_reusable_qb_used` is already set (nested execution via `processEventsWhileBlocked()`) the client allocates a private buffer instead.
- `processInputBuffer()` parses ahead up to `server.lookahead` commands into `c->pending_cmds`, but clamps the lookahead to 1 for unauthenticated clients because AUTH changes the handling of succeeding commands.
- Request type is inferred from the first byte: `'*'` selects `PROTO_REQ_MULTIBULK` → `processMultibulkBuffer()`, anything else selects `PROTO_REQ_INLINE` → `processInlineBuffer()`; an unknown `reqtype` triggers `serverPanic("Unknown request type")`.
- `processCommand()` resolves argv to a `struct redisCommand` through a three-tier lookup: an already-set `c->lookedcmd`, then `isCommandReusable(c->lastcmd, ...)` reusing the previous command, then a full `lookupCommand(c->argv, c->argc)`.
- The command table itself is populated once at startup by `populateCommandTable()` walking the generated `extern struct redisCommand redisCommandTable[]` from commands.def, so adding a command is a data change in `src/commands/*.json` followed by codegen.
- Module command filters get first look at every non-reprocessed command: `processCommand()` calls `moduleCallCommandFilters(c)` and `reqresAppendRequest(c)` before command lookup, meaning a module can rewrite argv before the core ever resolves the command name.
- `call()` records `dirty = server.dirty` and `old_master_repl_offset = server.master_repl_offset` before invoking the proc, and clears `CLIENT_FORCE_AOF|CLIENT_FORCE_REPL|CLIENT_PREVENT_PROP` so the handler must opt back into propagation.
- `server.allowed_propagate_targets` is a global mask narrowed for the duration of each `call()` so that nested `RM_Call`/Lua `redis.set_repl()` restrictions from an outer call cannot be escaped by an inner one — this is what makes effect-command rewriting (SPOP→SREM) honour selective replication.
- `propagateNow()` is the single fan-out point to the three output sinks: `feedAppendOnlyFile()` when `server.aof_state != AOF_OFF && target & PROPAGATE_AOF`, then `replicationFeedSlaves(server.slaves, ...)` and `asmFeedMigrationClient(argv, argc)` when `target & PROPAGATE_REPL`.
- `propagateNow()` asserts that propagation never happens while replica actions are paused — `serverAssert(!(isPausedActions(PAUSE_ACTION_REPLICA) && !server.client_pause_in_transaction))` — because a dataset change during a replica pause could be lost on failover.
- Output ordering in `beforeSleep()` is load-bearing and commented as such: `connTypeProcessPendingData()` and `clientCompressionProcessPendingData()` must run before `flushAppendOnlyFile(0)`, which must run before `handleClientsWithPendingWrites()` and `sendPendingClientsToIOThreads()` so `appendfsync=always` is satisfied before the reply is sent.
Behavior traces
Three flows dominate. Startup: `main()` → config → subsystem init → `initServer()` → `InitServerLast()` → `loadDataFromDisk()` → AOF reconciliation → `aeMain()`. Command execution: socket readable → `readQueryFromClient()` → `processInputBuffer()` → `processCommandAndResetClient()` → `processCommand()` → `call()` → handler → `alsoPropagate()` → `propagatePendingCommands()` → `propagateNow()`. Replica bootstrap: `replicaofCommand()` → `replicationSetMaster()` → `connectWithMaster()` → `syncWithMaster()` handshake → `slaveTryPartialResynchronization()` → full sync via `readSyncBulkPayload()`, with the master side running `syncCommand()` → `startBgsaveForReplication()` → `rdbSaveToSlavesSockets()`.
- STARTUP step 1 — `main()` seeds entropy from `time(NULL)^getpid()^tv.tv_usec` into `srand`/`srandom`/`init_genrand64`, calls `crc64_init()`, captures `umask(server.umask = umask(0777))` early to avoid a thread race, and seeds the dict hash with 16 bytes from `getRandomBytes()` — this is what makes hash-flooding attacks impractical.
- STARTUP step 2 — argv parsing in `main()` conceptually appends every `--opt value` pair onto the config file text as newline-separated directives, with documented precedence 'File, stdin, explicit options -- last config is the one that matters', plus special-cases for bare `--save` and `--sentinel`.
- STARTUP step 3 — `initServer()` installs `SIG_IGN` for SIGHUP/SIGPIPE, runs `setupSignalHandlers()`, `ThreadsManager_init()`, `makeThreadKillable()`, then `hashTemplatesInit()`, `createSharedObjects()`, `adjustOpenFilesLimit()` and `monotonicInit(monotonicLogCallback)` before creating the event loop.
- STARTUP step 4 — `initServer()` decides memory accounting once and irreversibly: `server.memory_tracking_enabled = server.key_memory_histograms || clusterSlotStatsEnabled(CLUSTER_SLOT_STATS_MEM)`, documented as disable-able at runtime but never re-enable-able to avoid a full slot re-scan.
- STARTUP step 5 — `InitServerLast()` starts the background subsystems last: `bioInit()`, `initThreadedIO()`, `set_jemalloc_bg_thread()`, and records `server.initial_memory_usage = zmalloc_used_memory()` as the baseline for memory reporting.
- STARTUP step 6 — the load sequence in `main()` is `aofLoadManifestFromDisk()` → `loadDataFromDisk()` → `aofSetupAfterPreloadFile()` → `aofOpenIfNeededOnServerStart()` → `aofDelHistoryFiles()` → `applyAppendOnlyConfig()`, the last of which exists specifically to apply an `appendonly` config change that arrived while loading.
- STARTUP step 7 — in cluster mode `main()` hard-asserts `verifyClusterConfigWithData() == C_OK` after load, then logs 'Ready to accept connections' per non-NULL `server.listeners[j]`, notifies systemd with `READY=1` when `supervised_mode == SUPERVISED_SYSTEMD`, and finally calls `warnAboutInsecureConfig()`.
- COMMAND step 1 — `processCommand()` first postpones the client via `blockPostponeClient(c)` if a module is in a blocked yield that excludes `BUSY_MODULE_YIELD_CLIENTS`, then runs `commandCheckExistence()` and `commandCheckArity()`, rejecting via `rejectCommandSds()`.
- COMMAND step 2 — commands literally named `host:` or `post` are intercepted before any other handling and routed to `securityWarningCommand(c)` returning `C_ERR`, a cross-protocol-scripting (HTTP-to-Redis) defence.
- COMMAND step 3 — commands carrying `CMD_INTERNAL` are made to look nonexistent (`cmd = NULL`) unless the client has `CLIENT_INTERNAL` or `mustObeyClient(c)` is true, so masters and AOF replay are implicitly trusted while ordinary clients cannot even discover them.
- COMMAND step 4 — `CMD_PROTECTED` commands are gated by `allowProtectedAction(server.enable_debug_cmd, c)` for `debugCommand` and `server.enable_module_cmd` for `moduleCommand`, with an error naming `enable-debug-command`/`enable-module-command`.
- COMMAND step 5 — `call()` sets `CLIENT_EXECUTING_COMMAND` to suppress mid-reply client-side-caching pushes, calls `enterExecutionUnit(1, call_timer)`, and skips slowlog/stat updates entirely when `isAOFLoadingContext()` is true.
Testing signals
Testing is a Tcl integration suite: `tests/test_helper.tcl` globs `tests/{unit,unit/type,unit/moduleapi,unit/cluster,integration}/*.tcl` into `::all_tests` and runs them against real spawned `redis-server` processes on ports from 21111. There are no C-level unit test files, but the binary embeds a `REDIS_TEST` mode invoked as `redis-server test <name>|all` for data-structure self-tests. Module API coverage is unusually deep — 48 C test modules under `tests/modules/` paired with 48 `unit/moduleapi/*.tcl` suites. CI runs eight build matrices plus an ASAN build and a commands.def drift check, with lcov/Codecov, CodeQL, Coverity, and a reply-schema linter on separate workflows.
- Test discovery is directory-glob based, not a manifest: `test_helper.tcl` iterates `set test_dirs {unit unit/type unit/moduleapi unit/cluster integration}` and lsorts `*.tcl` into `::all_tests`, so a new `.tcl` file in those dirs is picked up automatically.
- The harness is a client/server fan-out: `test_server_main`, `accept_test_clients`, `read_from_test_client` and `signal_idle_client` distribute test files across parallel Tcl clients, with `run_solo` for tests that must not run concurrently.
- Support libraries sourced by every suite live in `tests/support/`: `server.tcl` (start_server), `util.tcl`, `cluster_util.tcl`, `aofmanifest.tcl`, `redis.tcl` (the Tcl RESP client), `response_transformers.tcl` (RESP2/RESP3 normalisation), and `cli.tcl`.
- Module API is the most thoroughly covered subsystem: `runtest-moduleapi` enumerates 48 suites explicitly by `--single`, and `tests/modules/` holds 48 matching C test modules built by `$MAKE -C tests/modules` before the run.
- The C code carries an embedded self-test harness rather than a separate unit-test binary: `main()` under `#ifdef REDIS_TEST` accepts `redis-server test <name>|all` with flags `--accurate --large-memory --valgrind --verbose`, iterates a `redisTests[]` table, and prints a pass/fail summary.
- Cluster coverage includes 23 dedicated suites under `tests/unit/cluster/`, notably `atomic-slot-migration.tcl`, `internal-secret.tcl`, `packet-validation.tcl`, `tls-peer-impersonation.tcl` and `cluster-bus-port-protected-mode.tcl` — the security-sensitive cluster paths are explicitly exercised.
- Integration coverage focuses on durability and replication edge cases: `aof-multi-part.tcl`, `aof-race.tcl`, `corrupt-dump-fuzzer.tcl`, `corrupt-dump.tcl`, `psync2*.tcl` (four files), `replication-rdbchannel.tcl`, `replication-iothreads.tcl`, `failover.tcl`, `shutdown.tcl`.
- The primary CI job `test-ubuntu-latest` builds with `make REDIS_CFLAGS='-Werror' BUILD_TLS=yes` and runs `./runtest --verbose --tags -slow --dump-logs` — meaning slow-tagged tests do not gate PRs.
- CI enforces codegen freshness: the `validate commands.def up to date` step touches `src/commands/ping.json`, runs `make commands.def`, and fails if `git diff` is non-empty — a hand-edited commands.def will break the build.
- A dedicated `test-sanitizer-address` job builds `SANITIZER=address REDIS_CFLAGS='-Werror -DDEBUG_ASSERTIONS -DREDIS_TEST' BUILD_TLS=module` and reruns the suite, so debug assertions and the embedded C tests are compiled in on that path only.
- The build matrix covers portability rather than behaviour: `build-debian-old` (debian:buster container), `build-macos-latest`, `build-32bit` (via gcc-multilib), `build-libc-malloc`, `build-centos-jemalloc` (centos:stream9) and `build-old-chain-jemalloc` — all compile-only, no test step.
- Coverage is reported but non-gating: `codecov.yml` sets both `patch` and `project` statuses to `informational: true`, and the workflow runs `make lcov` and uploads `./src/redis.info`, gated on `github.repository == 'redis/redis'` so forks skip it.
Risk map
The dominant risk class is memory-safety in hand-written C parsing code reachable pre-authentication: the RESP parser, the cluster bus packet decoder, and the RDB/DUMP loader all consume untrusted bytes. Authentication is layered and easy to misconfigure — protected-mode, ACL, an auto-generated cluster internal secret, and module-supplied auth callbacks. A second class is coupling risk: the propagation invariants in `call()`/`propagateNow()`, the ordering contract inside `beforeSleep()`, and the object-type registration checklist all fail silently or assert if a contributor misses a step. Finally, there is genuinely dead code — GCRA is compiled out and its test file is `if 0`'d — and a large `DEBUG` surface with 50+ subcommands guarded only by a config flag.
- The RESP parser is the pre-auth attack surface: `processMultibulkBuffer()` and `processInlineBuffer()` parse attacker-controlled bytes into `pendingCommand` structs before any AUTH check, and `readQueryFromClient()` performs manual pointer arithmetic (`(size_t)(c->bulklen+2)-(sdslen(c->querybuf)-c->qb_pos)`) to size big-argument reads.
- The cluster internal secret is auto-generated with `getRandomHexChars(server.cluster->internal_secret, CLUSTER_INTERNALSECRETLEN)` and converged across nodes by a lexicographic `memcmp(...) > 0` rule in the PING extension handler — meaning any node that can join the bus can influence the shared secret used to authenticate internal connections.
- Internal-secret password comparison correctly uses `time_independent_strcmp()`, but it is one of several parallel auth paths (`checkPasswordBasedAuth`, `ACLAuthenticateUser`, module auth callbacks) that must all stay consistent — a change to one does not automatically cover the others.
- `moduleLoad()` calls `dlopen(path, RTLD_NOW|RTLD_LOCAL)` on an operator-supplied path, giving any successfully loaded module full in-process code execution; the only guard is that `MODULE` carries `CMD_PROTECTED` and is gated by `server.enable_module_cmd`.
- `warnAboutInsecureConfig()` only logs — it never refuses to start. A server with `DefaultUser` NOPASS, `protected-mode no` and a `0.0.0.0`/`::` bind emits a WARNING and then serves traffic.
- `redis-server --test-memory <mb>` and `--check-system` run arbitrary memory-stressing code (`memtest()`, `syscheck()`) directly from `main()` before any config or privilege handling, and `--test-memory` with a non-numeric argument silently becomes `atoi(...) == 0`.
- `src/debug.c` dispatches roughly 52 `DEBUG` subcommands via a chain of `strcasecmp(c->argv[1]->ptr, ...)` comparisons; the entire surface is protected only by the `enable-debug-command` config and `allowProtectedAction()`, and several subcommands (JMAP, SEGFAULT, SET-ACTIVE-EXPIRE, QUICKLIST-PACKED-THRESHOLD) can corrupt or crash a live server.
- Adding a new object type is a documented multi-file checklist with no compiler enforcement — the `server.h` comment requires coordinated edits to `rdb.c` (save/load, possibly RDB_VERSION), `aof.c` (rewrite), `db.c` (`obj_type_name`, `copyCommand`), `debug.c` (`xorObjectDigest`, `serverLogObjectDebugInfo`) and `defrag.c` (`defragKey`). Missing one produces silent data or defrag corruption, not a build error.
- GCRA is dead code in the shipped build: `src/gcra.c` is entirely inside `#ifdef ENABLE_GCRA`, `OBJ_GCRA`/`OBJ_TYPE_MAX 9` are conditional in `server.h`, the guard appears in `rdb.h`, `redismodule.h` and `acl.c`, and `tests/unit/gcra.tcl` disables its whole body with `if 0 {`. Building with `-DENABLE_GCRA` would activate untested code and change `OBJ_TYPE_MAX`, which is an RDB-visible constant.
- `beforeSleep()` encodes at least four ordering invariants in comments rather than in code structure (TLS pending data before AOF flush; `clusterBeforeSleep()` before `blockedBeforeSleep()`; `blockedBeforeSleep()` before `flushAppendOnlyFile`; AOF flush before `handleClientsWithPendingWrites`). Reordering any of them breaks `appendfsync=always` durability guarantees without failing a test deterministically.
- `propagateNow()` contains two assertions that convert logic errors into crashes rather than data loss: `debugServerAssert(duration != PROP_DURATION_UNKNOWN)` and the replica-pause assert. Both are reachable from any new command that queues propagation incorrectly.
- The query-buffer pooling scheme (`thread_reusable_qb` / `thread_reusable_qb_used` / `CLIENT_IO_REUSABLE_QUERYBUFFER`) is shared mutable state whose correctness depends on nested-execution detection; the code comment notes the private-buffer fallback exists only for `processEventsWhileBlocked()` re-entry.
Extension points
Redis has four first-class extension mechanisms. New commands are added declaratively: drop a JSON spec in `src/commands/`, implement the proc, and regenerate `src/commands.def`. New configuration parameters are appended to the `static_configs[]` table in `src/config.c` using the `create*Config` macro family. New runtime behaviour is added out-of-process via the module API (`RM_CreateCommand`/`RM_CreateSubcommand` plus data types, filters, and hooks). New scripting languages plug into `functionsRegisterEngine()`. On top of these, this tree adds a fifth: `modules/modules.yaml` as a declarative registry for bundled external modules.
- Command registration is codegen-driven: `src/commands/*.json` is the input, `utils/generate-command-code.py` is the generator, and `src/Makefile`'s rule `$(COMMANDS_DEF_FILENAME).def: commands/*.json ../utils/generate-command-code.py` regenerates `src/commands.def`. CI fails if the checked-in file drifts.
- The generator maps declarative JSON to C enums via `ARG_TYPES` (string/integer/double/key/pattern/unix-time/pure-token/oneof/block) and `GROUPS`, which already contains the newer `COMMAND_GROUP_ARRAY` and `COMMAND_GROUP_RATE_LIMIT` — adding a command group means editing this table.
- The runtime command registry is `redisCommandTable[]` from commands.def, loaded once by `populateCommandTable()` into dicts consulted by `lookupCommandLogic()`, `lookupCommandBySds()` and `lookupCommandByCString()` — module commands are inserted into the same dicts.
- Configuration is a single append-only table: `standardConfig static_configs[]` in src/config.c, populated by 119 `createBoolConfig`/`createStringConfig`/`createEnumConfig`/`createSDSConfig`/etc. calls, each taking a flags mask (`MODIFIABLE_CONFIG`, `IMMUTABLE_CONFIG`, `DEBUG_CONFIG`, `HIDDEN_CONFIG`, `SENSITIVE_CONFIG`) plus optional validate and apply callbacks.
- Config apply-callbacks are the hook for cross-subsystem side effects — e.g. `updateClusterFlags` on `cluster-replica-no-failover`, `updateJemallocBgThread` on `jemalloc-bg-thread`, `updateAppendonly` on `appendonly`, `updateRequirePass` on `requirepass`, and `applyClusterBusPortProtectedMode`.
- The module command API is `RM_CreateCommand(ctx, name, cmdfunc, strflags, firstkey, lastkey, keystep)` with `RM_CreateSubcommand(parent, ...)` for containers — the canonical way third-party code adds commands without touching the core tables.
- Modules can intercept every command before dispatch by registering a command filter, invoked as `moduleCallCommandFilters(c)` at the top of `processCommand()`; `src/module.c` documents a flag to prevent a module's own `RM_Call()` from re-entering its filter.
- Server lifecycle hooks are exposed as module server events — `beforeSleep()` fires `moduleFireServerEvent(REDISMODULE_EVENT_EVENTLOOP, REDISMODULE_SUBEVENT_EVENTLOOP_BEFORE_SLEEP, NULL)` once per loop iteration when `moduleCount()` is non-zero.
- Scripting languages are pluggable through `functionsRegisterEngine(const char *engine_name, engine *engine)`, which stores into a module-level `static dict *engines`; `FUNCTION STATS` reflects the registry back to clients, and the only current registration is `LUA_ENGINE_NAME` from `luaEngineInitEngine` in function_lua.c.
- Keyspace notification is a central hook point via `notifyKeyspaceEvent(type, event, key, dbid)` and the newer `notifyKeyspaceEventWithSubkeys(...)` — new data types must call these to participate in keyspace events and client-side-cache invalidation.
- Connection types are a runtime-registered abstraction: `connTypeInitialize()` in `main()` sets up `server.listeners[j]` for `j < CONN_TYPE_MAX`, with implementations in `src/socket.c`, `src/unix.c` and `src/tls.c` behind `src/connection.h` — TLS can even be built as a loadable module with `BUILD_TLS=module`.
- Bundled external modules are added declaratively by appending an entry to `modules/modules.yaml` with `name`/`repo`/`ref`/`target_module`/`loadmodule` and optional `build_env` — no new Makefile is needed, because `modules/common.mk` derives `MODULE_NAME` from `$(notdir $(CURDIR))` and reads the rest from the manifest.
Contributor handoff
Read `src/server.h` (the 4,976-line master header that every `.c` file includes) and `src/server.c` before touching anything — nearly all global state hangs off the `server` struct declared there. Never hand-edit `src/commands.def`; edit the JSON spec and regenerate. Prefer adding config via the `static_configs[]` table and features via the module API over touching the dispatch path. Build with `make REDIS_CFLAGS='-Werror'` to match CI, and validate with `./runtest --tags -slow` plus `./runtest-moduleapi` if you touched `src/module.c`. Be aware this tree's root `Makefile` is not upstream's: plain `make` runs `scripts/build.sh`, which builds `src/` and then every cloned external module.
- Read first, in order: `src/server.h` (all struct/constant/prototype definitions), `src/server.c` (`main`, `initServer`, `processCommand`, `call`, `beforeSleep`, `serverCron`), `src/networking.c` (client lifecycle and RESP parsing), and `src/config.c` (`static_configs[]`).
- Never edit `src/commands.def` by hand. Edit `src/commands/<cmd>.json`, run `make commands.def` (which invokes `utils/generate-command-code.py`), and commit both — CI's `validate commands.def up to date` step diffs the regenerated output and fails on drift.
- Match CI's build flags locally: `make REDIS_CFLAGS='-Werror' BUILD_TLS=yes`. Use `REDIS_CFLAGS`/`REDIS_LDFLAGS` rather than `CFLAGS`/`LDFLAGS` — the src/Makefile header notes that plain CFLAGS propagate into the `deps/` builds (jemalloc, Lua, hiredis) too.
- Test commands to run: `make -C src test` (= `./runtest`) for the default suite, `./runtest --tags -slow --dump-logs` to match CI exactly, `./runtest --single unit/type/hash` to scope, `make -C src test-modules`, `test-cluster`, `test-sentinel` for the other three harnesses.
- Trap — building Redis alone is `make -C src all`, not `make`. The root `Makefile`'s default goal is `build`, which runs `scripts/build.sh` and will additionally try to build every module cloned under `modules/*/src`. Use `make build redis` or `make build core` to skip modules.
- Trap — on macOS you must use `gmake` (GNU Make ≥ 4.x), not Apple's `/usr/bin/make` 3.81. The root Makefile explicitly `export MAKE`s so recipe shells inherit the right binary, precisely because non-interactive recipe shells otherwise PATH-resolve to make 3.81.
- Trap — adding a new object type requires coordinated edits to `rdb.c`, `aof.c`, `db.c` (`obj_type_name`, `copyCommand`), `debug.c` (`xorObjectDigest`, `serverLogObjectDebugInfo`) and `defrag.c` (`defragKey`), per the checklist comment in server.h. Nothing enforces this at compile time.
- Trap — `redis.conf` is the tracked, hand-edited core config with no markers; `redis-full.conf` is untracked and regenerated by `scripts/sync-redis-conf.sh` on every `make modules-update`. Never hand-add `loadmodule` lines to `redis.conf`.
- Safe edit boundary — prefer adding behaviour through the module API (`RM_CreateCommand`, module data types, command filters, server-event hooks) or a new `static_configs[]` entry over modifying `processCommand()`/`call()`, which carry assertions and propagation invariants.
- Do not touch `deps/` — it is vendored third-party source (jemalloc, lua, hiredis, linenoise, hdr_histogram, fpconv, xxhash, tre) built by separate targets listed in `DEPENDENCY_TARGETS`. Changes there are lost on dependency refresh.
- Style convention — the codebase is C99/gnu11 compiled with `-pedantic -Wall -W -Wstrict-prototypes -Werror=deprecated-declarations`, and falls back to `-std=c99` (also setting `SKIP_VEC_SETS=yes`) when the compiler lacks C11 `_Atomic`. New code must build under both.
- Licensing convention — every new source file carries the Redis tri-license header (RSALv2 / SSPLv1 / AGPLv3); contributions are governed by the Software Grant and CLA reproduced in full in CONTRIBUTING.md.
Languages
| Language | Files | Share of source |
|---|---|---|
| C | 788 | 82% |
| JSON | 465 | docs/config |
| Shell | 83 | 9% |
| Python | 48 | 5% |
| Markdown | 27 | docs/config |
| YAML | 22 | docs/config |
| Lua | 20 | 2% |
| Ruby | 9 | 1% |
Start reading here
Probable source entrypoint: src/ — inferred from the directory tree and manifest, not the upstream docs.
Source roots
src/ (693 files)
Repository structure
| Top-level path | Files |
|---|---|
deps/ | 707 |
src/ | 693 |
tests/ | 306 |
modules/ | 66 |
utils/ | 40 |
.github/ | 16 |
scripts/ | 13 |
tools/ | 1 |
Docs found: CONTRIBUTING.mdREADME.md
CI & config entrypoints
README.md— Repository readme.github/workflows/post-release-automation.yml— GitHub Actions workflow.github/workflows/codecov.yml— GitHub Actions workflow.github/workflows/reply-schemas-linter.yml— GitHub Actions workflow.github/workflows/codeql-analysis.yml— GitHub Actions workflow.github/workflows/modules-build-flow.yml— GitHub Actions workflow.github/workflows/coverity.yml— GitHub Actions workflow.github/workflows/spell-check.yml— GitHub Actions workflow.github/workflows/daily.yml— GitHub Actions workflow.github/workflows/external.yml— GitHub Actions workflow
Structural health signals
Average score 7.9/10 across 23 analyzed source files; 3 hotspots surfaced.
tools/array-bench.py— 431 lines, score 5.9 (Deep nesting, High churn)utils/req-res-log-validator.py— 364 lines, score 5.9 (Deep nesting, High churn)utils/generate-command-code.py— 635 lines, score 6.2 (Large file, Deep nesting)
These are deterministic review leads — size, churn, and structural signals — not proof of a bug in this project.
Recent history
c3215f019a4c2026-09-22 — CI remove unused redis_docs_sync workflow (#15816)0d6266f2deb92026-09-21 — Fix XTRIM leaving a 0-live stream node that RDB cannot load (#15815)62d1a5b930ed2026-09-20 — Make ASM tests work with --single (#15636)5f08991bce472026-09-20 — Fix argument release race in the AOF duration test module (#15805)c00f920b2a3d2026-09-19 — redis-cli: use CLIENT NO-TOUCH for key size analysis (#15817)