Redisredisstringshasheslistssets

Redis: Data Structures, Not a Cache

Redis is an in-memory server of data structures — strings, hashes, lists, sets, sorted sets, streams — with atomic operations and per-key expiry; caching is the most common use, not the defining one.

▶ InteractiveInterview questionSee how this works internally →
Progress

What it is

A single-threaded server holding data structures in memory, executing one command at a time. Because it is single-threaded, every command is atomic without locks: INCR cannot lose an increment, ZADD cannot race, SET NX is a correct lock acquisition. Because it is in memory, a command takes microseconds and a single instance serves hundreds of thousands of them per second. Because it is data structures rather than bytes, you can update one field of a hash, push to one end of a list, or ask for the rank of one member without reading and rewriting the whole value.

The structures and what each is for

String: one value, up to 512 MB; SET/GET, INCR for atomic counters, SET … EX for cached values with expiry. Hash: a map of fields; one object without serialising it, HINCRBY for per-field counters. List: a linked list; LPUSH/RPOP for a queue, LPUSH + LTRIM for "latest N". Set: unique members with O(1) membership, SINTER/SUNION for set algebra — "which of my friends are online". Sorted set: members with scores kept ordered, O(log n) rank and range — leaderboards, rate limiters, priority queues, time-indexed anything. Stream: an append-only log with ids and consumer groups — a lightweight Kafka.

Plus HyperLogLog (approximate distinct counts in 12 kB), bitmaps (billions of booleans), geospatial (radius queries on a sorted set), and pub/sub (fire-and-forget fan-out with no persistence).

Expiry, eviction, persistence

EXPIRE / SET … EX attach a TTL to a key; it is removed lazily on access and actively by a background sampler. When memory is full, maxmemory-policy decides what to evict: allkeys-lru for a pure cache, volatile-ttl to evict only keys with expiry, noeviction to refuse writes. Choose deliberately — the default noeviction turns a full cache into an outage.

Persistence is optional and partial. RDB snapshots at intervals; AOF logs every command with a configurable fsync. Neither makes Redis a system of record: an appendfsync everysec loses up to a second of writes on crash, and always costs most of the throughput. Anything you cannot recompute or re-fetch does not belong only in Redis.

Atomic recipes

Rate limit: INCR key then EXPIRE key 60 on the first hit — two commands, so wrap them in MULTI/EXEC or a Lua script to be atomic. Lock: SET lock:x token NX EX 30, release with a Lua script that checks the token before DEL. Leaderboard: ZINCRBY, ZREVRANGE … WITHSCORES, ZREVRANK. Session: a hash with a TTL, refreshed on each request. Distributed counter: INCRBY and periodic flush to the database. Lua scripts run atomically on the server and are how you compose commands without races.

A correct lock
1-- acquire: only if absent, with a timeout so a dead holder cannot hold it forever
2SET lock:order:42 <random-token> NX EX 30
3
4-- release: only if we still hold it (atomic check-and-delete)
5EVAL "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) end return 0"
6 1 lock:order:42 <random-token>

Key points

  • Single-threaded → every command atomic; in-memory → microseconds; data structures → partial updates.
  • Strings, hashes, lists, sets, sorted sets, streams: pick the structure that matches the access pattern.
  • TTL per key; eviction policy must be chosen; persistence is partial — never the only copy.
  • Compose atomic operations with MULTI or Lua; SET NX EX is the lock primitive.

Redis, one command at a time

Redis, one command at a time
A real in-memory Redis subset. Step through a scenario or type your own commands. SLEEP n advances the simulated clock so TTLs are visible.

The simplest structure: a key holding one value, optionally with an expiry. This is 80% of caching.

redis> press “Next command” or type below
Keyspacet = +0s
(empty)
Keys
0
With TTL
0

When to use — and when not

Use it when
  • Caching, sessions, rate limiting, counters, leaderboards, queues with modest durability needs, pub/sub fan-out, coordination locks.
Avoid it when
  • As the system of record for anything you cannot lose.
  • Data larger than memory.
  • Queries by anything other than key — there is no WHERE clause.
  • Multi-key transactions with rollback semantics.

Failure modes

  • maxmemory-policy noeviction turning a full cache into write failures.
  • Lock released without checking the token, deleting someone else’s lock.
  • Keeping the only copy of an order in Redis.
  • A hot key saturating one instance.

See how this works internally →

Descend one layer: the same topic explained from the machinery up.