Redis deep-dive · Part 1 of 10. Next: The data types as a design toolkit.
Most people meet Redis as “the fast key–value cache.” That’s true, but it’s the least interesting thing about it — and it hides the one idea that makes the whole system click. Redis is an in-memory data structure server that runs your commands one at a time, on a single thread. Hold onto that sentence. Almost every strength, quirk, and footgun in Redis falls out of it.
We’ll hang the examples on something you already know: a backend API with a Postgres database behind it, and Redis sitting alongside for the things a relational database is bad at — counters, caches, queues, rate limiters.
The mental model: one thread, in memory
A traditional database assumes its data lives on disk and that many queries run at once. Redis assumes the opposite: the dataset lives in RAM, and commands execute serially through a single-threaded event loop. It reads a command, runs it to completion, then reads the next one. There is no second command running “at the same time.”
That sounds like a limitation — one CPU core doing all the work? — but it buys
something valuable: every single command is atomic, for free. No two commands
can interleave, because there is no interleaving. When Redis executes INCR, it
reads the number, adds one, and writes it back with zero chance of another client
sneaking in between those steps.
A worked example: rate limiting
Say you want “max 100 requests per user per minute.” In your app server against
the database, you’d SELECT the count, check it, and UPDATE count + 1 — a
read-modify-write race. Two concurrent requests both read 99, both decide they’re
under the limit, both write 100. You’d need a transaction or a row lock.
In Redis it collapses to two commands:
INCR ratelimit:user:42 # atomic; returns the new count
EXPIRE ratelimit:user:42 60 # first hit sets the 60-second window
INCR returns the post-increment value atomically. Ten app servers hammering the
same key at once each get a distinct, correctly-ordered count — because Redis
serialises them on its one thread. No lock, no race, no coordination in your app.
That is the property you’re buying, and it’s why counters, locks and rate
limiters are such a natural fit for Redis.
The flip side: one slow command hurts everyone
The same serial execution that gives you free atomicity is also the sharpest
footgun in the system. Because one thread does everything, a single slow command
blocks every other client. Ask Redis to run KEYS * on a million-key database,
or SMEMBERS on a ten-million-element set, or a heavy SORT, and the whole
server stalls while that one O(n) command grinds through — every other request,
for every other key, waits behind it.
This is the fingerprint of most Redis latency incidents: latency spikes and every client is slow at once, not just the one that issued the bad command. Redis is blindingly fast because each operation is usually O(1) or O(log n) and lives in RAM. It stays fast only if you keep each operation small. Hand it one O(n) command on a large value and you’ve turned your low-latency datastore into a stop-the-world pause.
So the two things to carry forward:
- Serial execution → single-command atomicity is free. Reach for Redis when you need atomic counters, locks, and read-modify-write without app-side coordination.
- Serial execution → one heavy command hurts everyone. Keep operations small and fast; O(n) commands on big values are the classic incident.
Fast, but not infinitely scalable per instance
If one thread executes all commands, one Redis process can only use one core for that work — no matter how many cores the box has. That isn’t a speed problem (a single instance still does 100k+ ops/sec, precisely because it avoids lock contention and context-switching), but it is a throughput ceiling.
Where this goes next
Everything in this series is a consequence of the two ideas above. Atomic transactions (Part 4) matter because commands are serial. Persistence (Part 7) matters because the data lives in volatile RAM. Sharding (Part 8) matters because one thread can only do so much. And the whole prod-debugging chapter (Part 10) is really about finding the one command or the one big key that’s monopolising that single thread.
Next, we turn the single thread into a design tool: Redis isn’t a flat map of strings, it’s a server of data structures, and picking the right one is the difference between an O(1) operation and a server-stalling O(n) one.
If you can explain why
INCRis safe without a lock, and why a giantSMEMBERSis dangerous, you already understand Redis better than most people who use it daily.