Skip to content
Dev Dump

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.

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 = 10
Thread B reads K = 10 ← stale read
Thread A writes K = 11
Thread B writes K = 11 ← lost update; expected 12

In Redis, 10 clients sending INCR simultaneously still land on exactly +10. Never 9, never 11.

k++ race condition vs Redis INCR atomicity

Multi-threaded k++ races to a lost update; Redis INCR is one atomic command — no mutex needed.

Data lives in RAM — access is nanosecond-fast. Persistence is configurable:

ModeHowTrade-off
RDBPeriodic snapshot to diskFast restart; may lose last N seconds of writes
AOFAppend every write command to a logNear-zero data loss; larger footprint
NonePure cacheFastest; data gone on crash

Other features:

  • TTL / expirySET key val EX 1800 auto-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.

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 call read() on those. Redis uses this.

Multi-threaded concurrency versus single-threaded IO multiplexing

Multi-threading gives true parallelism but needs locks; Redis runs one thread and an event loop that only touches sockets with data — atomic, lock-free.
1. Accept any new TCP connections
2. Ask OS (epoll / kqueue): which sockets have data?
3. Read only the ready sockets → execute command (~1µs)
4. Repeat

Concrete: 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.

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:

PrefixTypeExample
+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 stringsput K V becomes *3\r\n$3\r\nput\r\n$1\r\nK\r\n$1\r\nV\r\n:

RESP encoding of the put K V command

put K V → a RESP array of three bulk strings. The server reads prefixed lengths and never guesses where a value ends.

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.