What is Redis?
Verified on Redis 8.10.2 (
redis:8.10.2-alpine), ioredis 6.0.0, Node 24, on an Apple-silicon Mac. Every output block below is copied from a real run.
In-memory key-value data store
Section titled “In-memory key-value data store”Redis stores every key and its associated value directly in RAM. When your application sets a key, the data lives in memory — no disk seek, no page fault, no file-system overhead. When it reads the key back, the answer comes from the same memory. This is why Redis latency is consistently sub-millisecond even under heavy load.
Each value is typed. Redis is not a simple string bag — it understands data structures natively: Strings, Lists, Hashes, Sets, Sorted Sets, Streams, and more. The server can operate on those structures atomically, which means you can append to a list, increment a counter, or pop from a queue without any application-level locking.
Command execution is single-threaded — the process is not
Section titled “Command execution is single-threaded — the process is not”Redis executes commands on one thread. That thread reads a command from a socket, runs it to completion, writes the reply, then moves to the next one. Nothing else is allowed to touch the keyspace while a command runs.
“Redis is single-threaded” is the half-truth people repeat, so look at the real process. This is ps -T (one line per thread) inside the container, right after start:
PID USER TIME COMMAND 1 redis 0:00 redis-server *:6379 26 redis 0:00 {bio_close_file} redis-server *:6379 27 redis 0:00 {bio_aof} redis-server *:6379 28 redis 0:00 {bio_lazy_free} redis-server *:6379 29 redis 0:00 {jemalloc_bg_thd} redis-server *:6379 30 redis 0:00 {jemalloc_bg_thd} redis-server *:6379Thread 1 runs your commands. The bio_* threads do background work that must never block it (closing files, fsync for the AOF, freeing big values lazily — see UNLINK later), and jemalloc_bg_thd is allocator housekeeping. INFO threads shows the I/O side, and io-threads is the knob:
127.0.0.1:6379> PINGPONG127.0.0.1:6379> CONFIG GET io-threads1) "io-threads"2) "1"127.0.0.1:6379> SET language "Redis"OK127.0.0.1:6379> TYPE languagestringStarting a second throwaway server with redis-server --io-threads 4 adds more I/O threads (INFO threads then lists io_thread_0 to io_thread_3), and Redis 8 reworked how they read and write client sockets. Even then, command execution stays on the main thread — I/O threads only parse and write bytes. Source: Redis 8.0 what’s new and the io-threads section of the shipped redis.conf.
This design has two consequences:
- Speed — no lock contention on the data, no context switch between commands. One core is enough for hundreds of thousands of simple commands per second.
- Each command is atomic — while one command runs, no other command can observe half of it. That is per command. Two commands in a row are two separate turns of the loop, and another client can run in between (you will prove that below).
Data-structure server, not just a cache
Section titled “Data-structure server, not just a cache”The phrase “Redis is a cache” is common but incomplete. A plain cache maps keys to opaque blobs. Redis maps keys to rich data structures you can manipulate on the server side:
| Type | Description |
|---|---|
| String | Text, numbers, or raw bytes up to 512 MB |
| List | Ordered sequence; push/pop from either end |
| Hash | Field-value map inside a single key |
| Set | Unordered collection of unique members |
| Sorted Set | Set where each member carries a numeric score |
| Stream | Append-only log of timestamped entries |
Because operations happen server-side, you avoid round trips. Instead of GET → modify locally → SET, you do LPUSH or ZINCRBY in one network call.
Redis 8 — one distribution, no modules
Section titled “Redis 8 — one distribution, no modules”For years the extra capabilities lived in modules you installed separately, and “Redis Stack” was the bundle that shipped them together. Redis 8 merges the two: what used to be Redis Community Edition is now called Redis Open Source, and the formerly modular features are built into the server itself. Ask the real server what it loaded (trimmed to names and versions):
127.0.0.1:6379> MODULE LIST # names + ver only; each entry also has path/args vectorset ver 1 bf ver 81001 (RedisBloom, path .../redisbloom.so) timeseries ver 81001 search ver 81001 ReJSON ver 81000And the built-in JSON type works out of the box, with no install step:
127.0.0.1:6379> JSON.SET user:1 $ '{"name":"Ada"}'OK127.0.0.1:6379> JSON.GET user:1"{\"name\":\"Ada\"}"127.0.0.1:6379> TYPE user:1ReJSON-RL127.0.0.1:6379> DEL user:1(integer) 1| Also built in since Redis 8 | What it gives you |
|---|---|
| JSON | Store and update JSON documents in place with JSONPath |
| Search and query | Secondary indexes, full-text search, and vector search over hashes or JSON |
| Time series | Downsampling, aggregation, and retention for timestamped samples |
| Probabilistic | Bloom filter, Cuckoo filter, Count-Min sketch, Top-K, t-digest |
| Vector set (beta) | A native type for vector similarity search |
Redis 8 also landed what the project calls its largest performance jump — the release notes cite over 30 optimizations, up to 87% lower command latency and up to 18% faster replication (Redis 8.0 what’s new; these are vendor numbers, not measured here) — plus the three hash commands you will meet in the Hashes lesson.
Licence and forks — what “Redis” means in 2026
Section titled “Licence and forks — what “Redis” means in 2026”Which server you run matters for legal and for compatibility reasons, so know the timeline:
| Date | Event | Source |
|---|---|---|
| up to Redis 7.2 | BSD-3-Clause open source | — |
| 2024-03-28 | Redis moves to dual source-available RSALv2 / SSPLv1 | redis.io: dual source-available licensing |
| 8 days later | Linux Foundation announces Valkey, a BSD-licensed fork of Redis 7.2.4 (first release 7.2.5) | valkey.io: history |
| 2025-05-01 | AGPLv3 added as a third licence option starting with Redis 8 | redis.io: AGPLv3 |
This course teaches Redis Open Source 8.x. Valkey speaks the same wire protocol and its core commands (strings, lists, hashes, sets, sorted sets, streams, MULTI, Lua) match the 7.2 line, but it diverges on newer features. Commands introduced in Redis 8 — HGETEX, HSETEX, HGETDEL (8.0), XACKDEL (8.2), SET ... IFEQ and DELEX (8.4) — are called out with their version where they appear, so you know when a lesson will not run on an older server or on Valkey. I did not run a Valkey container for this lesson: the compatibility statement comes from the docs above, not from a test.
Common use cases
Section titled “Common use cases”| Use case | Redis feature |
|---|---|
| Cache | TTL-based expiry on any key |
| Session store | Hash per session ID, fast reads |
| Task queue | LPUSH / BRPOP on a List |
| Leaderboard | Sorted Set with ZADD / ZRANK |
| Rate limiter | INCR + EXPIRE on a counter key |
| Pub/Sub messaging | PUBLISH / SUBSCRIBE channels |
A quick type check
Section titled “A quick type check”Run PING to confirm the server is alive, then use TYPE to inspect any key (the first session above did exactly that):
PINGReading AI-generated code
Section titled “Reading AI-generated code”An assistant writes a page-view counter and justifies it with the sentence everyone knows:
🐛 Spot the bug
import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL!);
// Redis is single-threaded, so this read-modify-write is safe.async function recordVisit() {const n = Number((await redis.get('visits')) ?? 0);await redis.set('visits', n + 1);}
await Promise.all(Array.from({ length: 50 }, recordVisit));console.log(await redis.get('visits'));Show the answer
Run against the 8.10.2 container, the last line prints 1, not 50. “Single-threaded” only guarantees that each of the two commands runs alone; GET and SET are two turns of the event loop, and the other 49 GETs all ran before the first SET landed. It is the classic lost update. The fix is to make it one command:
// @runimport { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL!);await redis.del('b1:visits');await Promise.all(Array.from({ length: 50 }, () => redis.incr('b1:visits')));console.log(await redis.get('b1:visits')); // "50"await redis.del('b1:visits');redis.disconnect();The same test printed 50. You will meet INCR properly in the Counters lesson.
Mental Model
Section titled “Mental Model”Think of Redis as one very fast clerk at a single counter. Customers (clients) queue up; the clerk finishes one request completely before looking at the next, so nobody ever sees a half-finished request. Helpers in the back room (background threads) shred paper, sync the ledger to disk and free shelves, but they never serve the counter. The clerk is fast because the shelves are RAM, and the counter rule is why each command is atomic. A customer who needs two separate requests treated as one must ask for it explicitly: a single multi-step command, MULTI, or a Lua script.
Trade-offs
Section titled “Trade-offs”| Choice | Benefit | Cost |
|---|---|---|
| Store data in RAM (in-memory) | Sub-millisecond latency; reads and writes are far faster than disk | RAM costs much more than disk, and the whole dataset must fit in available memory |
| Single-threaded command execution | No lock contention; every command is atomic by construction; easy to reason about | One slow command stalls every client; one instance executes commands on one core (scale out with sharding, not threads) |
| Redis Open Source 8 (RSALv2 / SSPLv1 / AGPLv3 options) | Built-in JSON, search, time series, probabilistic types | Not BSD any more; check the licence with your legal team, or evaluate Valkey (BSD) if that matters |
Common Misconceptions
Section titled “Common Misconceptions”- “Redis is single-threaded.” Command execution is. The process has background threads (
ps -T) and optional I/O threads (io-threads). Getting this wrong makes people believe multi-command sequences are atomic, or that Redis cannot use more than one core at all. - “Redis is just a cache.” It is a data-structure server. Whether it is only a cache depends on your persistence and
maxmemoryconfiguration, not on the software. - “Redis is open source, so it is BSD.” It was, until 7.2. Today there are three licence options (RSALv2, SSPLv1, AGPLv3); Valkey is the BSD fork.
- “Redis Stack is a separate product you install.” Not since Redis 8 —
MODULE LISTon the plain image shows JSON, search, time series and Bloom already loaded.
Common mistakes
Section titled “Common mistakes”- Assuming Redis is durable by default — the defaults favor speed, not durability. If you need data to survive a restart, you must configure persistence (RDB/AOF) yourself.
- Letting the dataset grow without setting
maxmemory— because everything lives in RAM, an unbounded dataset with no eviction policy configured can use up all available memory and OOM the machine. - Assuming single-threaded means “slow” — the single command thread is exactly what makes Redis fast, since there is no context switching. But it also means one expensive command (e.g.
KEYS *on a large dataset) blocks every other client until it finishes. The First Commands lesson measures that.
Real-world example
Stack Overflow ran an L1 (local memory)/L2 (Redis) cache in front of SQL Server: a request checks local cache, then Redis, and only queries the database on a miss — at a reported scale of roughly 160 billion Redis operations a month across their fleet (Stack Overflow: The Architecture – 2016 Edition, Nick Craver, 2016 — a ten-year-old number, treat it as history).