Back to Notes

Redis

Redis

Redis is an open-source in-memory data structure store, usable as:

  • Database
  • Cache
  • Message broker
  • Streaming engine

It provides data structures out of the box:

  • hash, list, set, sorted set, bitmap, hyperloglog
  • geospatial indexes, streams

These build a variety of applications:

  • Real-time chat
  • Message buffers
  • Gaming leaderboards
  • Auth/session store
  • Media streaming
  • Real-time analytics

What makes Redis special?

  • Every operation is atomic — while a command executes, Redis does not context-switch to another command. Examples:
    • Putting a key
    • Adding to a list
    • Set union / intersection
    • Incrementing a value
  • Data is stored in-memory → most common use is caching. Redis also offers configurable persistence:
    • Periodically dumping data to disk (RDB snapshot)
    • Write-ahead log of all commands (AOF)
    • No persistence at all
  • Other key features: Transactions, Pub/Sub, TTL on keys, LRU eviction.

Concurrent Programming Models (single process)

Concurrent programming = doing more than one thing at the same time.

Multi-threading

  • Each incoming request is accepted by the server and executed in a separate thread.
  • Example:
    • R1 → INCR K → T1
    • R2 → INCR K → T2
  • Ensuring data correctness: if K = 10 and two threads run K++, possible final values are 11 and 12 (lost update). Other threads must wait while one is in the critical section:
    ACQ LOCK
    	K++
    REL LOCK
    
  • Done via mutex / semaphores (pessimistic locking).

I/O Multiplexing (apparent concurrency)

The event loop uses this.

  • I/O is blocking — e.g. reading from a socket: read() blocks until the other side sends.
  • So you can't just invoke read() on a socket unless you know data is coming.
  • Handling each connection on a separate thread is one solution (while one thread blocks, another runs) — but needs mutex/semaphores to protect critical sections.
  • Better question: can we not wait on I/O — get notified when there's movement?
  • That is I/O multiplexing.
  • Core idea: use I/O monitoring syscalls (select / poll / epoll / kqueue) to watch many sockets, and fire read only on the ones that have data.

![[Pasted image 20260628140849.png]]

What Redis exploits to be fast

  • Network I/O is slow → waiting to receive commands.
  • In-memory ops are fast → once a command arrives, Redis executes it quickly.
  • So Redis made two conscious design choices:
    1. Single-threaded → no mutexes/semaphores, no lock waiting.
    2. I/O multiplexing → handle many TCP connections concurrently on that one thread.

Talking the Redis Language — RESP

  • To understand a client, the server needs a protocol: Redis Serialisation Protocol (RESP).
  • RESP supports common datatypes (int, string, arrays) and a way to convey errors.
  • Redis sends a command as an array of strings:
    • PUT K V["PUT", "K", "V"] → serialised with RESP.

RESP description

  • RESP is a request–response protocol: client sends request in RESP, server responds in RESP.
  • Every datatype starts with a special character and ends with \r\n (CRLF).
TypePrefixExample
Simple String++PONG\r\n
Integer::1729\r\n
Bulk String$$4\r\nPONG\r\n
Array**2\r\n$4\r\nLLEN\r\n$6\r\nmylist\r\n
Error--Key not found\r\n

Simple Strings

  • Start with +, then the string, then CRLF.
  • Minimal overhead: n + 3 bytes.
  • Eg. +PING\r\n, +PONG\r\n.

Integers

  • Start with :, then the integer, then CRLF. Eg. :1729\r\n.

Bulk Strings

  • Start with $, then byte count, CRLF, the actual string, CRLF. Eg. $4\r\nPONG\r\n.
  • Why bulk strings if we have simple strings?
    1. Simple strings are not binary-safe (can't contain arbitrary bytes).
    2. Simple strings cannot contain \r\n (it's the terminator).
    3. Bulk strings can store any binary data — even a PNG.
  • Special forms:
    • Empty string: $0\r\n\r\n
    • Null value: $-1\r\n (length -1 signals absence of data)

Arrays

  • Start with *, then element count, CRLF, then RESP-encoded elements.
  • Eg. ["a", 200, "cat"]*3\r\n$1\r\na\r\n:200\r\n$3\r\ncat\r\n (no spaces).
  • Null array: *-1\r\n · Empty array: *0\r\n · Nesting allowed.

Errors

  • Start with -, then message, then CRLF. Eg. -Key not found\r\n.

Why RESP over JSON

JSON is inefficient — needs parsing, quote management, gets bulky. RESP is:

  1. Human-readable
  2. Simple
  3. Performant
  4. Prefixed-length → you know exactly how many bytes to read.

TODO — Build Your Own Redis (hands-on)

Implementation project + per-stage guide (Python & Go): [[build-your-own-redis/README]]

  • Stage 1 — Simple TCP server, 1 concurrent connection
  • Stage 2 — RESP encoding/decoding
  • Stage 3 — PING → PONG
  • Stage 4 — I/O multiplexing for many connections
  • Stage 5 — Command dispatch + SET / GET
  • Stage 6 — TTL / expiry

Interview Trade-Offs (Redis)

  • Single-threaded + I/O multiplexing > thread-per-connection for an in-memory store: no lock contention, cache-friendly, and CPU is never the bottleneck (network is). Trade-off: one slow command (KEYS *, big SORT) blocks everything → avoid O(n) commands in hot paths.
  • RESP vs JSON/HTTP: prefixed-length binary-safe framing beats delimiter-parsing for a high-throughput protocol.
  • Persistence: RDB (compact, fast restart, risk losing last window) vs AOF (durable, replayable, larger/slower) — often run both.

Related

  • [[synthesis/DDIA Interview Map]] · [[synthesis/Job Switch Hub]]
  • Redis playlist arc S1–S7: [[Coach/project_dsa_sd_curriculum]]