Redis gets mentioned in system design discussions constantly. Cache this. Rate limit that. Leaderboard here. Pub/sub there. It's one of those tools that seems to solve everything — which usually means it's either genuinely versatile or people are overusing it. In Redis's case, it's genuinely versatile. But understanding why it works the way it does makes the difference between using it well and creating subtle production problems.
Here's the simplified version.
What Redis Actually Is
Redis is an in-memory data structure store. Everything lives in RAM. There's no disk read on the hot path — when you GET a key, Redis fetches it directly from memory and returns it. That's the entire reason it's fast.
It's also single-threaded. One command runs at a time. No locking, no concurrency primitives, no "which thread modified this first" bugs. Operations are atomic by definition. This design choice trades theoretical parallelism for practical simplicity — and in most real workloads, you're not CPU-bound, you're network-bound. Single-threaded Redis with sub-millisecond latency almost never becomes the bottleneck.
The numbers: a single Redis node handles around 100,000 writes per second with sub-millisecond read latency. For most applications, that's more than enough headroom.
The Data Structures
Redis isn't just a key-value store — it's a collection of data structures you can operate on atomically. That distinction matters. Here are the ones you'll actually use:
- Strings. The default. Store any value — text, JSON, serialized objects, integers. Atomic increment (INCR) works on numeric strings, which is how counters and rate limiters are built.
- Hashes. A map within a key. Instead of serializing an entire user object to a string and deserializing it on every read, you store fields individually. Update one field without touching the rest.
- Lists. Ordered sequences with O(1) push/pop from either end. Natural fit for queues and recent-activity feeds.
- Sets. Unordered collections of unique values. Useful for tracking membership — "has this user seen this notification?" — with O(1) add, remove, and contains.
- Sorted Sets. Sets where every member has a score. Members are ordered by score. O(log N) insertion and ranked lookup. This is the data structure behind every leaderboard Redis serves.
- Streams. Append-only logs with consumer groups. Each entry gets an auto-generated ID. Consumers track their own position. Useful for event pipelines and work queues.
What Redis Is Used For
Caching
The most common use case. Your database query takes 200ms. Redis has the result in under 1ms. You set a TTL (time-to-live) on the key — after that, it expires and the next request hits the database and repopulates the cache.
The failure mode to know: the hot key problem. If a single key gets hit by a disproportionate amount of traffic — a viral post, a trending product — all that traffic concentrates on the one Redis node holding that key. The node saturates. Solutions: cache the hot item locally on your app servers with a short TTL, or duplicate it across multiple keys and distribute reads.
Rate Limiting
Two approaches, each simple to implement:
- Fixed window. INCR a key like
rate:user:123:2026-08-02-14(per-hour bucket). Set an EXPIRE on first increment. If the count exceeds your limit, reject the request. The key expires at the end of the window, resetting the counter automatically. - Sliding window. Store each request timestamp in a Sorted Set, with the timestamp as the score. To check the rate: ZREMRANGEBYSCORE to drop old entries, ZCARD to count what's left in the window, ZADD to record the current request. More accurate than fixed window, slightly more expensive.
Distributed Locks
When multiple servers need to coordinate — "only one worker should process this job" — Redis provides a simple lock primitive.
The pattern: SET lock:resource-id unique-token NX PX 5000. NX means "only set if not exists." PX 5000 means "expire in 5 seconds." If the SET returns OK, you have the lock. If it returns nil, someone else does.
Release is the tricky part: you must only delete the key if your token matches — otherwise you might release a lock held by someone else if yours expired. This check-and-delete must be atomic, which is why it's done with a Lua script.
The honest caveat: Redis replication is asynchronous. A lock written to the primary might not have reached replicas before a failover. In that scenario, two workers could hold the same lock simultaneously. For workloads where that's unacceptable, use a coordination service with stronger consistency guarantees. For most practical distributed locks where losing a bit of work is acceptable, Redis is fine.
Leaderboards
Sorted Sets make leaderboards trivial. ZADD adds a user with their score. ZRANK gives their rank. ZRANGE with WITHSCORES returns the top N entries. All of this is O(log N). The implementation that would be painful in a relational database is a few commands in Redis.
Pub/Sub
Clients subscribe to channels. Publishers send messages to channels. Redis routes messages to all active subscribers in real time. Delivery is "at most once" — if a subscriber is disconnected when a message is published, it misses it. No persistence, no replay.
This is appropriate for real-time notifications where missing a message is acceptable and you'll get the next one soon anyway: live activity feeds, presence indicators, collaborative editing cursors. It's not appropriate for reliable event delivery where every message must be processed — use Streams with consumer groups for that.
When Not to Use Redis
Redis is not a database. It is not a replacement for PostgreSQL. Three specific situations where Redis is the wrong tool:
- You need durability. Redis's persistence options — periodic snapshots (RDB) and write logging (AOF) — both have gaps. A crash can lose acknowledged writes. If you cannot afford to lose data, Redis is not your primary store.
- Your working set exceeds RAM. Everything in Redis lives in memory. Memory is expensive. If you're storing terabytes of data, Redis becomes cost-prohibitive as a primary store (though it can still cache a hot subset of it).
- You need relational queries. Redis has no joins, no SQL, no cross-key aggregations unless your keys live on the same cluster node. If your access patterns are complex and relational, a database is the right tool.
Scaling Redis
A single Redis node can get you surprisingly far. When you need more, Redis Cluster distributes your keyspace across multiple nodes using 16,384 hash slots. Every key maps to a slot; every slot is owned by a node. Clients cache the slot-to-node mapping and talk directly to the right node for each operation.
The scaling lever you control is key design. Hash tags ({user:123}:posts and {user:123}:profile) force related keys onto the same node, enabling multi-key operations on them. Keys without hash tags are distributed independently, which spreads load but prevents cross-key operations.
Replication in Redis Cluster is asynchronous — writes are acknowledged before replicas receive them. Fast, but with the same durability caveat as standalone Redis.
The Mental Model
Redis is a fast, in-memory layer that sits in front of slower systems and handles the operations those systems are bad at doing quickly: caching expensive reads, coordinating distributed processes, maintaining real-time counters and rankings, and broadcasting events.
It does these things extraordinarily well because it's designed around one constraint: everything stays in memory, and every operation is atomic. That constraint is also its limit. Stay within it, and Redis is one of the most reliable tools you'll use.