POST
Redis is more than a cache
Jul 20267 MIN READ
Redis has a branding problem. Ask most developers what it is for and they will say "caching", and they will be right, and they will also be missing most of the story. Redis is a data structure server. The caching use case is the most visible one, but the same tool that holds your hot reads in memory also has the data structures that make it a natural fit for queues, locks, counters, sessions, and rate limiters. The teams that only use it as a cache are leaving most of the value on the table.
The flip side of that breadth is the danger. Redis is in memory, and it is configured to be fast, and a lot of the interesting things you can build with it have a footgun attached: the data lives in a server that can lose it. The discipline that separates the good Redis users from the ones who lose data is knowing which problems Redis should solve and which problems it should never solve.
The data structures are the product
The reason Redis is more than a cache is that it is a small library of data structures with network semantics. The string is the obvious one, and it is the cache primitive: set a key, get a key, expire it. But the string also has atomic increment, which makes it a counter. The counter is the primitive for rate limits, view counts, stock counts, anything where you need "increment and get the result" without a race.
The list is a queue, with blocking operations that let workers wait for work instead of polling. The set is a membership test, useful for "is this user in the allowed list" and for deduplication. The sorted set is the interesting one: an ordered collection, useful for leaderboards, for "give me the next N items after this score", and for time based sorting. The hash is a small record, useful for storing a session or a profile without a separate table.
Each of these has operations that are atomic in the server, and atomicity is the real product. A cache gives you speed. A data structure server gives you speed plus the guarantee that the read, check, and write happened as one unit, which is the difference between a rate limiter and a rate limiter with a race condition.
The patterns that are not caching
The queue is the pattern that replaces a whole class of infrastructure. Push a job onto a list, have a worker block on it, pop and process. This is not as sophisticated as a full message broker with routing and acknowledgments and dead letter queues, and for a huge fraction of jobs it does not need to be. The blocking pop is the hidden gem: a worker that blocks on the list waits efficiently, and the moment work arrives, it is handed the job. The teams that discovered they did not need a message broker for their background jobs are the teams that read the Redis documentation past the cache examples.
The lock is the pattern that prevents the double run. The SET with NX (not exists) and an expiry is the distributed lock primitive: one process wins the set, the others get nothing, and the expiry is the safety valve that releases the lock if the winner crashes. The double submit problem, the cron job that should run once, the migration that should not run on two instances, these are all the same shape, and Redis solves them in a few lines.
The session store and the rate limiter are the other classics, and they share the property that makes them natural Redis problems: the data is ephemeral, the access pattern is read heavy and write heavy at the same time, and losing the data is annoying but not catastrophic. Sessions expire, rate limit windows reset, and the counter can start over. That is the pattern that separates the right Redis uses from the wrong ones.
The source of truth question
Here is the question that decides everything: what happens when this Redis instance loses the data? If the answer is "we lose some sessions and everyone logs in again", that is a Redis shaped problem. If the answer is "we lose the orders", that is not a Redis shaped problem, and no amount of convenience changes that.
Redis can be configured to survive restarts, with snapshots and append only files, and it can be set up with replication and clustering. The honest framing is that Redis is a fast data store that can be made durable with effort, and the effort is worth it for some data and not for others. The data that is cheap to recreate, or that does not matter when it is gone, belongs in Redis. The data that must survive a crash belongs in a database, even if the database is slower, because the crash is not a matter of if, it is a matter of when.
The pattern that respects this is cache aside, or the write through pattern with care: the database is the source of truth, and Redis holds the hot copies, and the invalidation strategy is what keeps the copies honest. The teams that get into trouble are the ones that start treating Redis as the primary store because it is fast, and then discover that fast and durable are not the same thing.
The cache that is not your friend
The classic cache bug is the cache that serves stale data forever because the invalidation was never wired. The cache is fast, the cache is never updated, and the users see the old value for months. The discipline is the same as for any cache: the write path and the invalidation path are part of the feature, not an afterthought.
The other classic is the cache stampede: the hot key expires, and every request that finds the cache empty tries to recompute the value at the same time, and the database that the cache was protecting takes the full load of every request at once. The fix is the lock pattern from earlier: one request recomputes, the rest wait for the lock or serve a stale value. The stampede is the moment the cache stops protecting the database and starts causing the outage.
When to reach for it, and when not to
The honest sizing question is whether the problem needs a data structure server at all. A single instance with in memory state is simpler than Redis, and for a small service, the extra server is complexity without benefit. The switch to Redis earns its keep when the service runs on multiple instances, when the state needs to be shared, when the operations need to be atomic, or when the data should survive a deploy.
The other side of the sizing question is whether the problem needs a real queue instead of a Redis list. The line is the same line that separates the job that can be retried from the job that cannot be lost. A job that must be exactly once, that needs acknowledgments and redelivery, that must survive a broker crash, is a message broker's job. A job that can be retried, that can tolerate loss, that is idempotent, is a Redis list's job. The teams that draw this line before they build are the teams that do not have a queue incident in their future.
The one server to rule them all
Redis is the Swiss army knife of backend infrastructure, and like a Swiss army knife, it is excellent at many jobs and the wrong tool for the ones that need the dedicated version. Use it for the hot reads, the counters, the queues that can lose a job, the locks, the sessions, the rate limits. Do not use it for the orders, the accounts, the audit logs. The teams that respect that line get a small, fast, versatile server that removes a whole class of infrastructure. The teams that do not respect it get a data loss incident with a very fast postmortem.
The question to ask before every Redis use case is the one that has never failed me: if this server forgets, are we fine? The answers to that question, asked honestly, are the design of your system.