← Writing
3 min read

Redis Is More Than a Cache

Most teams use Redis as a key-value cache and stop there. But its data structures make it a rate limiter, a queue, a leaderboard, and a pub/sub bus — here's the Redis I actually reach for on backends.

RedisBackendDistributed SystemsDatabases

Ask most developers what Redis is and you'll hear "a cache." It is a fantastic cache. But reducing Redis to GET/SET is like calling a Swiss Army knife a bottle opener. Its real power is a set of in-memory data structures with atomic operations, and once you see them, half the auxiliary services you were about to build turn into a few Redis commands.

I've leaned on it across multiple backends. Here's the Redis beyond the cache.

Why in-memory changes what's possible

Redis keeps data in RAM, which makes operations sub-millisecond. That speed is what lets it serve roles a disk-based database would be too slow for — a rate-limit check on every request, a leaderboard updated on every score. The trade is that RAM is finite and volatile, so Redis is for data that's hot, ephemeral, or reconstructable — not your source of truth.

Caching, done right

Start with the obvious use, but do the part people skip — set an expiry:

SET user:42 "{...}" EX 3600     # cache for 1 hour, then auto-expire

The EX is not optional. A cache without expiry is a memory leak with extra steps, and stale-forever data is worse than no cache. Every cached key should have a TTL that reflects how long the data can safely be wrong.

Rate limiting

This is my most-used non-cache pattern. Redis's atomic INCR plus an expiry gives you a per-user request counter in two commands:

INCR   rate:user:42       # atomic; returns the new count
EXPIRE rate:user:42 60    # first hit starts a 60s window
# if the returned count > limit, reject with 429

Because INCR is atomic, concurrent requests can't race past the limit — the count is always exact. No table, no locks, no separate service.

A job queue

A Redis list is a queue: push on one end, pop (blocking) off the other. A producer enqueues work; a worker waits for it.

LPUSH  jobs "{...}"        # producer adds a job
BRPOP  jobs 0             # worker blocks until a job arrives

This is the backbone of the background-processing I've built — get slow work off the request path and let a pool of workers drain the queue. Libraries like Bull wrap exactly this with retries and scheduling, but the primitive underneath is a Redis list.

Leaderboards and rankings

A sorted set keeps members ordered by a score, and stays sorted as you update it. That's a leaderboard for free:

ZADD   leaderboard 1500 "user:42"      # add/update a score
ZREVRANGE leaderboard 0 9 WITHSCORES   # top 10, already ranked
ZREVRANK leaderboard "user:42"         # this user's exact rank

Computing ranking from a relational table means an ORDER BY over the whole set on every read. A sorted set maintains the order incrementally, so "top 10" and "my rank" are instant.

Pub/sub and ephemeral state

Two more that come up constantly: pub/sub for broadcasting events to subscribers (I've used it behind Socket.io to fan real-time updates across server instances), and simple shared ephemeral state — sessions, feature-flag caches, "who's online" sets — that multiple app instances need to read fast and don't need to persist forever.

The mental model

Stop thinking "Redis = cache" and start thinking "Redis = fast, atomic data structures I can share across processes." The question becomes: is this data hot, short-lived, or reconstructable, and would an in-memory structure make it trivial? Rate limits, queues, leaderboards, sessions, real-time fan-out — all yes. Keep your source of truth in a durable database, and let Redis absorb the fast, ephemeral, high-frequency work it's uniquely good at. That division of labor is one of the most reliable performance patterns I know.