Foundations
Redis is an open-source, in-memory data-structure store — usable as a database, cache, message broker, or streaming engine. Every command is atomic, data lives in RAM, and the server runs on a single thread with an event loop.
Built-in structures: strings · hashes · lists · sets · sorted sets · bitmaps · HyperLogLog · geospatial indexes · streams.
Common uses: session store · leaderboards · rate limiters · real-time analytics · pub/sub · queues.
Atomicity
Section titled “Atomicity”Every Redis command runs to completion before the next starts — no interleaving, no context switch mid-command. The single thread serializes everything without locks.
Why it matters — the k++ problem in a multi-threaded system:
Thread A reads K = 10Thread B reads K = 10 ← stale readThread A writes K = 11Thread B writes K = 11 ← lost update; expected 12In Redis, 10 clients sending INCR simultaneously still land on exactly +10. Never 9, never 11.
In-memory + persistence
Section titled “In-memory + persistence”Data lives in RAM — access is nanosecond-fast. Persistence is configurable:
| Mode | How | Trade-off |
|---|---|---|
| RDB | Periodic snapshot to disk | Fast restart; may lose last N seconds of writes |
| AOF | Append every write command to a log | Near-zero data loss; larger footprint |
| None | Pure cache | Fastest; data gone on crash |
Other features:
- TTL / expiry —
SET key val EX 1800auto-deletes after 30 min; without it, forgotten keys accumulate. - Eviction — when memory is full, Redis evicts keys (LRU, LFU, random) rather than crashing.
- Pub/Sub — publish to a topic; all subscribers receive it immediately (push-based).
- Transactions — a block of commands runs atomically, nothing interleaved.
Why single-threaded — and still fast
Section titled “Why single-threaded — and still fast”Redis runs everything on one thread:
- No mutexes, no deadlocks, guaranteed atomicity for free
- Commands are pure in-memory ops — microseconds each
- The slow part is the network, not the CPU
The problem a single thread must solve: how do you serve many connections at once?
- Multi-threading — one thread per client, true parallelism, but shared state needs locks. Ready threads end up blocked waiting on a mutex.
- IO multiplexing — one thread, apparent concurrency. Instead of a blocking
read()per socket, ask the OS: which sockets have data right now? Only callread()on those. Redis uses this.
The event loop
Section titled “The event loop”1. Accept any new TCP connections2. Ask OS (epoll / kqueue): which sockets have data?3. Read only the ready sockets → execute command (~1µs)4. RepeatConcrete: four open connections (C1–C4). OS says C1 ready → execute INCR counter. OS says C3 ready → execute LPUSH queue job. No socket ready → accept C5, C6. Each command is ~1µs; the loop races ahead. Network IO is slow; Redis commands are fast — the event loop fills wait time by serving other ready connections.
Talking to Redis — TCP + RESP
Section titled “Talking to Redis — TCP + RESP”Redis is a plain TCP server; redis-cli is a TCP client. Communication is raw bytes, so both sides need an agreed format: RESP (REdis Serialization Protocol). Each value starts with a one-byte type marker and ends with CRLF:
| Prefix | Type | Example |
|---|---|---|
+ | Simple string | +PONG\r\n |
- | Error | -key not found\r\n |
: | Integer | :1729\r\n |
$ | Bulk string (binary-safe, length-prefixed) | $4\r\nPONG\r\n |
* | Array | *3\r\n... |
Commands are sent as arrays of bulk strings — put K V becomes *3\r\n$3\r\nput\r\n$1\r\nK\r\n$1\r\nV\r\n:
Bulk strings are binary-safe — length declared up front means bytes can contain anything, even \r or NUL. Prefix-length everywhere means the reader always knows how many bytes are coming — no over-reads, no blocking guesses.
Why not JSON? Too bulky to parse (quote scanning, escaping). RESP is human-readable, minimal-overhead, and trivially fast.