Redis explained

Redis Use Cases: Caching, Sessions, Rate Limits, Queues

Redis keeps data in memory and answers most commands in well under a millisecond, which is why it sits in front of so many databases. But "add Redis" covers very different jobs. A cache can lose any key and recover from the database; a session store or a job queue cannot. Getting that distinction right decides your eviction policy, your memory size and whether you need backups, and it matters more than any choice between Redis and Valkey.

By , Founder at SeaGit

·

Key takeaways

  • Split Redis data into two kinds: rebuildable (a cache) and the only copy (sessions, queues, counters, locks). They need opposite eviction policies.
  • A pure cache wants allkeys-lru or allkeys-lfu. Anything that is the only copy wants noeviction, plus backups. BullMQ, for example, requires noeviction.
  • Redis Cluster shards data across nodes. You need it when your data outgrows one machine’s memory or one core’s throughput, which most apps under a few GB never reach.
  • Replication and failover are a separate question from sharding. A single node with persistence and nightly backups suits a cache and many small apps; it does not suit data that must survive a node loss without downtime.

First decide: can you lose this key?

Every pattern below falls into one of two groups. Rebuildable data is a copy of something that lives elsewhere: a cached query result, a rendered page fragment, an API response. Losing it costs one slow request. Only-copy data exists nowhere else: a login session, a queued job, a rate-limit counter, a lock. Losing it logs a user out, drops a job or lets a burst through.

Redis decides what to delete when it reaches its memory limit (maxmemory) using the eviction policy. With allkeys-lru it deletes the least recently used key, any key. With noeviction it deletes nothing and rejects writes with an OOM error instead. If you mix both kinds of data in one instance under an allkeys policy, Redis will eventually evict a session to make room for a cache entry. Use volatile-lru (evicts only keys with a TTL) or, simpler, run two instances.

1. Caching database queries (cache-aside)

The most common use. The app checks Redis first; on a miss it reads the database, writes the result to Redis with a TTL and returns it. Typical targets are product pages, user profiles, permission lookups and the results of expensive aggregate queries. A cache hit replaces a database round trip, often several milliseconds plus query time, with a sub-millisecond lookup, and it takes load off the database at peak.

javascript
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);

async function getProduct(id) {
  const key = `product:${id}`;
  const hit = await redis.get(key);
  if (hit) return JSON.parse(hit);

  const product = await db.products.findById(id);   // the slow path
  await redis.set(key, JSON.stringify(product), 'EX', 300); // 5-minute TTL
  return product;
}

// on update, delete the key so the next read refreshes it
async function updateProduct(id, fields) {
  await db.products.update(id, fields);
  await redis.del(`product:${id}`);
}
Cache-aside with a TTL as a safety net and explicit invalidation on write.

Settings

  • Eviction: allkeys-lru, or allkeys-lfu when a few keys are read far more than the rest.
  • Size: the hot working set, not the whole table. A cache that holds 10% of your rows often serves most reads.
  • Backups: optional. An empty cache only means a slow first minute.

2. Session storage

When an app runs on more than one pod, sessions in process memory break: the next request may land on another pod. Storing sessions in Redis with a TTL equal to the session lifetime fixes that, and logging a user out everywhere becomes a single DEL.

javascript
// store: 30-minute sliding session
await redis.set(`sess:${sid}`, JSON.stringify(session), 'EX', 1800);

// on each request, read and extend
const raw = await redis.get(`sess:${sid}`);
if (raw) await redis.expire(`sess:${sid}`, 1800);

A session is only-copy data: use noeviction and size memory for your peak number of active sessions. Most frameworks have a Redis session store (connect-redis for Express, Django’s cache sessions, Spring Session Data Redis).

3. Rate limiting an API

A fixed-window rate limiter is two commands: increment a counter named after the client and the current window, and set it to expire when the window ends. Because INCR is atomic, every pod sees the same count. The Redis INCR documentation uses this exact pattern.

javascript
// at most 100 requests per API key per minute
async function allow(apiKey) {
  const window = Math.floor(Date.now() / 60000);
  const key = `rl:${apiKey}:${window}`;
  const [[, count]] = await redis.multi().incr(key).expire(key, 60).exec();
  return count <= 100;
}

Counters are only-copy data. Under an allkeys policy a counter can be evicted mid-window and the client gets a fresh allowance. Use noeviction; the keys are tiny and expire on their own.

4. Background job queues

Sending email, resizing images, calling a slow third-party API: work that should not block a web request. Queue libraries such as BullMQ (Node.js), Sidekiq (Ruby) and RQ (Python) keep jobs in Redis, and Redis Streams give you a log with consumer groups if you want to build your own.

javascript
import { Queue, Worker } from 'bullmq';
const connection = { url: process.env.REDIS_URL };

const emails = new Queue('emails', { connection });
await emails.add('welcome', { to: 'ana@example.com' });

new Worker('emails', async (job) => sendEmail(job.data), { connection });

A queued job is the only copy of that work. BullMQ’s production guide says to set maxmemory-policy to noeviction, because an evicted key corrupts a queue. Queues are also where backups and persistence matter most: AOF keeps jobs across a restart, and a nightly snapshot covers a lost disk.

5. Leaderboards and rankings

Sorted sets keep members ordered by score, so the top ten or a player’s rank is one command, even with millions of entries. Games, quiz apps, “most viewed this week” and trending lists all use this.

bash
ZINCRBY leaderboard:week42 25 "player:ana"
ZREVRANGE leaderboard:week42 0 9 WITHSCORES   # top 10
ZREVRANK  leaderboard:week42 "player:ana"     # Ana's position, 0-based

Whether you can rebuild a leaderboard from your database decides the policy. If scores live only in Redis, treat them as only-copy data.

6. Locks and de-duplication

SET key value NX PX 30000 sets a key only if it does not exist, with a 30-second expiry. That makes a simple lock (one pod runs the nightly report) or a de-duplication guard (process each webhook delivery once).

javascript
// run only on one pod at a time; the lock frees itself after 30 s
const token = crypto.randomUUID();
const ok = await redis.set('lock:nightly-report', token, 'PX', 30000, 'NX');
if (ok) {
  try { await runReport(); }
  finally {
    // release only if we still hold it
    await redis.eval(
      "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) end",
      1, 'lock:nightly-report', token);
  }
}

A single-instance lock is fine for efficiency (avoiding duplicate work). For correctness, where two holders would corrupt data, the Redis documentation on distributed locks explains why you need more: a fencing token checked by the resource you are protecting.

When Redis is the wrong tool

  • As the only store for data you cannot lose, unless you have replication or at least persistence plus tested backups. Memory is the primary copy; disk is the safety net.
  • For data much larger than memory. Redis keeps the whole dataset in RAM; a 50 GB table belongs in a database with a cache in front of it.
  • For ad-hoc queries. Redis looks things up by key. If you need to filter by arbitrary columns, that is a database job.
  • To hide a slow query you could fix. An index often removes the need for the cache entirely, without the invalidation work.

Do you need Redis Cluster?

“Redis cluster” means two different things. Redis Cluster (capital C) is a mode that shards your keys across several primary nodes using 16,384 hash slots. Separately, people say “cluster” for a primary with replicas and automatic failover. They solve different problems.

Sharding (Redis Cluster)

You need it when your data no longer fits in one machine’s memory, or one primary cannot keep up with your write rate. It has costs: clients must be cluster-aware, and commands that touch several keys (MGET, transactions, Lua scripts) only work when all keys hash to the same slot, which means designing key names with hash tags such as {user:42}:cart. If your data is a few GB and your peak is a few thousand requests per second, one node handles it.

Replicas and failover

You need these when Redis being down for a minute is an outage: a session store for a large site, a queue that must not pause. A replica on another node takes over when the primary fails. For a cache, a restart only means a cold minute, so a single node with persistence is usually enough.

A practical rule: start with one node, persistence on, and backups. Add replicas when downtime becomes the risk, and shard when memory does.

Running Redis in your own AWS account

SeaGit’s Redis Stack deploys Valkey 8.1 or Redis 8.2 into a Kubernetes cluster in your AWS account from one form. You choose the eviction policy from the list above, the size (up to 1.5 GB of data) and who can reach it: apps in the cluster, anything in your VPC, or the internet over TLS limited to the IP ranges you allow. Backups go to an S3 bucket in the same account, new or existing, with optional KMS encryption: on deploy, nightly, and before delete. In our live runs a stack was ready to connect about three and a half minutes after Deploy on an existing cluster.

It is a single node with AOF persistence, which fits caches, sessions, rate limits and queues for most apps. It is not Redis Cluster and has no automatic failover; for those, a replicated managed service is the right tool. The Redis in Minutes guide walks through a deploy, and Back up Redis to S3 covers backups and restore.

Frequently asked questions

Redis Use Cases: Caching, Sessions, Rate Limits, Queues: FAQ

What is Redis mostly used for?

Caching database queries and API responses is the most common use. After that come session storage, rate limiting, background job queues, leaderboards with sorted sets, and locks or de-duplication with SET NX.

Which eviction policy should a Redis cache use?

allkeys-lru for a general cache, or allkeys-lfu when a small set of keys gets most reads. Use noeviction for anything that is the only copy of the data, such as sessions, queues and counters.

Do I need Redis Cluster for caching?

Usually not. Redis Cluster shards data across nodes and is needed when the dataset outgrows one machine’s memory or write throughput. Most app caches are a few GB or less and run on one node.

Is Valkey compatible with Redis?

Valkey is a fork of Redis 7.2 maintained under the Linux Foundation with a BSD licence. It speaks the same protocol, so Redis clients such as ioredis, redis-py and go-redis work unchanged.

Does a Redis cache need backups?

A pure cache does not: it refills from the database. Sessions, queues and anything else that exists only in Redis should be persisted and backed up, for example nightly RDB snapshots to S3.

Sources

Checked 12 October 2026.

  1. Redis: Key eviction — maxmemory, the eviction policies and how allkeys and volatile policies differ.
  2. Redis: INCR — The counter and rate-limiter patterns.
  3. Redis: SET — NX and PX options and the simple locking pattern.
  4. Redis: Distributed locks — Why single-instance locks need care, and when fencing is required.
  5. Redis: Scale with Redis Cluster — Hash slots, multi-key operations and hash tags.
  6. BullMQ: Going to production — The requirement to set maxmemory-policy to noeviction.

Read next