What Is Redis Used For? 7 Real Use Cases in Web Applications Beyond Caching

Short answer: Redis is an in-memory data structure store used as a cache, a database, a message broker and a coordination layer. In real web applications it is most often used for caching, session storage, rate limiting, real-time messaging (pub/sub), leaderboards and counters, background job queues, and distributed locks.

Almost every article on this topic stops at “Redis is a fast cache”. That is true, and it is also the least interesting thing about it. If you already have a Postgres or MySQL database, the real question is not what is Redis, it is: which problem on my current backlog does Redis actually solve better than my database?

This post answers that. Seven concrete use cases, each with the Redis data structure behind it, a short code snippet, and the trade-offs you should know before shipping it.

Redis in 60 seconds: why it is fast and why that matters

Redis (REmote DIctionary Server) keeps your data in RAM and serves it over a very simple protocol. Typical response times are sub-millisecond, and a single node routinely handles tens of thousands of operations per second on modest hardware. A Crash Course in Redis tackles the same question from another angle.

Three properties explain most of its use cases:

  • In-memory storage: no disk seek on the read path, so lookups are extremely cheap.
  • Atomic commands: each command (and each Lua script or transaction) runs to completion without interleaving, which makes counters, locks and queues safe without you writing locking logic.
  • Rich data structures: it is not just key to string. You get lists, hashes, sets, sorted sets, streams, bitmaps, HyperLogLog, geospatial indexes and vector indexes.

That last point is the one developers underuse. Picking the right structure is usually what turns a hard problem into five lines of code.

Data structure to problem mapping

Redis structure What it is good at Typical use case
String Blob storage with TTL, atomic counters Cache entries, feature flags, rate limit counters
Hash Objects with field level updates Sessions, user profiles, shopping carts
List FIFO / LIFO with blocking reads Simple job queues, activity feeds
Set Uniqueness, intersections Online users, tags, deduplication
Sorted set (ZSET) Ranking and range queries by score Leaderboards, sliding window rate limits, delayed jobs
Pub/Sub channel Fire and forget fan-out WebSocket broadcast across app servers
Stream Persistent log with consumer groups Event pipelines, reliable task distribution
Vector index Similarity search on embeddings RAG retrieval, semantic caching for LLM apps
redis database server

1. Caching (the baseline use case, done properly)

Yes, caching is the number one answer to “what is Redis used for”. The part most tutorials skip is the policy around it: key naming, TTL, and invalidation.

The classic pattern is cache-aside:

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

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

Practical rules that save you pain later:

  • Always set a TTL. A cache without expiry is a memory leak with extra steps.
  • Version your key prefixes (product:v2:). Changing the serialized shape then becomes a one-line deploy instead of a mass invalidation script.
  • Configure maxmemory and an eviction policy such as allkeys-lru on pure cache instances.
  • Beware of stampedes. When a hot key expires, hundreds of requests may hit the database at once. Add a short lock or a randomized TTL jitter.

2. Session storage for stateless app servers

The moment you run more than one application instance behind a load balancer, in-process sessions break: a user logs in on server A and the next request lands on server B. Storing sessions in Redis makes your app servers stateless and disposable.

// Express + connect-redis
import session from 'express-session';
import { RedisStore } from 'connect-redis';

app.use(session({
  store: new RedisStore({ client: redis, prefix: 'sess:' }),
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: { httpOnly: true, secure: true, sameSite: 'lax', maxAge: 86400000 }
}));

Why Redis beats a database table here: the session TTL is handled natively by Redis (no cron job to clean rows), writes happen on almost every request, and the data is cheap to lose compared to business data. In Spring Boot the equivalent is Spring Session with spring-session-data-redis; in Django it is django.contrib.sessions.backends.cache; in Laravel it is SESSION_DRIVER=redis.

Watch out: if your Redis instance is purely a cache with LRU eviction, do not put sessions on it. An eviction becomes a forced logout. Use a separate instance or logical database with noeviction.

3. Rate limiting and abuse protection

Rate limiting needs a shared, atomic counter that all your app instances can agree on. That is a textbook fit for Redis.

The simplest version is a fixed window with INCR plus EXPIRE, done atomically in Lua so two concurrent requests cannot both skip the expiry:

-- KEYS[1] = rate:login:ip:1.2.3.4 , ARGV[1] = window seconds
local current = redis.call('INCR', KEYS[1])
if current == 1 then
  redis.call('EXPIRE', KEYS[1], ARGV[1])
end
return current
# Python, redis-py
allowed = 20
count = limiter_script(keys=[f"rate:login:ip:{ip}"], args=[60])
if count > allowed:
    abort(429)

Need something smoother than a fixed window? Use a sorted set as a sliding window log: add one member per request scored by timestamp, drop everything older than the window, then count.

ZREMRANGEBYSCORE rate:api:user:42 0 <now_ms - 60000>
ZADD rate:api:user:42 <now_ms> <request_id>
ZCARD rate:api:user:42
EXPIRE rate:api:user:42 60

Fixed window is cheaper, sliding window is fairer. For very high volume endpoints, a token bucket in Lua gives you burst tolerance with a constant memory footprint.

redis database server

4. Pub/sub and real-time features across servers

You have WebSockets, three Node or Rails processes, and a message that must reach a user connected to a different process. Redis pub/sub is the standard glue.

# terminal A (consumer side, one per app instance)
SUBSCRIBE room:general

# terminal B (whatever process produced the event)
PUBLISH room:general '{"user":"amelie","text":"deploy done"}'
// Socket.IO Redis adapter: broadcast works across all instances
import { createAdapter } from '@socket.io/redis-adapter';
io.adapter(createAdapter(pubClient, subClient));
io.to('room:general').emit('message', payload);

Important limitation: classic pub/sub is fire and forget. If no subscriber is listening, the message is gone. For anything you cannot afford to lose, use Redis Streams instead, which persist entries and support consumer groups with acknowledgements:

XADD events:orders * type order_paid order_id 1042
XGROUP CREATE events:orders billing 0
XREADGROUP GROUP billing worker-1 COUNT 10 BLOCK 5000 STREAMS events:orders >
XACK events:orders billing 1699-0

5. Leaderboards, counters and analytics in real time

Ranking “top N” from a relational table means an ORDER BY over a growing dataset, often on every page load. A sorted set keeps the ranking maintained for you, and gives you a user’s exact rank in O(log N). It is the sort of thing a careful web development agency handles once and never revisits.

ZADD leaderboard:weekly 1500 user:42
ZINCRBY leaderboard:weekly 50 user:42

# top 10 with scores
ZREVRANGE leaderboard:weekly 0 9 WITHSCORES

# my own position (0-based)
ZREVRANK leaderboard:weekly user:42

# how many players scored between 1000 and 2000
ZCOUNT leaderboard:weekly 1000 2000

The same family of commands powers more than games:

  • Trending content: score by decayed popularity, read top slices.
  • Live counters: INCR page:views:2026-10-05 then flush aggregates to your database periodically.
  • Unique visitors at scale: PFADD / PFCOUNT (HyperLogLog) tracks millions of uniques in about 12 KB per key, with roughly 0.8 percent error.
  • Daily active flags: bitmaps with SETBIT / BITCOUNT, one bit per user per day.

6. Background job queues

Sending emails, generating PDFs, resizing images, calling a slow third-party API: none of that belongs in an HTTP request. Redis is the storage engine behind most popular job frameworks: Sidekiq (Ruby), Celery (Python), BullMQ (Node), Laravel Queues (PHP), RQ, Spring Batch integrations and more.

The raw primitive is a list with a blocking pop, so workers wait on the server instead of polling:

# producer
LPUSH queue:emails '{"to":"[email protected]","tpl":"welcome"}'

# worker: atomically move the job to a processing list (reliable queue pattern)
BLMOVE queue:emails queue:emails:processing RIGHT LEFT 0

In practice, use a library rather than writing retries, backoff and dead letter handling yourself:

// BullMQ
import { Queue, Worker } from 'bullmq';

const emails = new Queue('emails', { connection });
await emails.add('welcome', { userId: 42 }, {
  attempts: 5,
  backoff: { type: 'exponential', delay: 2000 },
  removeOnComplete: 1000
});

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

Delayed and scheduled jobs are usually implemented with a sorted set scored by execution timestamp, which is exactly the pattern above applied to time instead of points.

When not to use it: if you need strict ordering guarantees, long retention, replay of months of events or multi-consumer fan-out at huge scale, a dedicated broker such as Kafka or RabbitMQ is a better fit. For 95 percent of web applications, Redis-backed queues are more than enough.

redis database server

7. Distributed locks and idempotency guards

Two cron containers start the same nightly invoice job. Two webhook deliveries arrive for the same payment. You need one of them to win. Redis gives you an atomic “set if not exists with expiry”:

# acquire: unique token, 10 second lease
SET lock:invoices:2026-10 f7c1a9e3 NX PX 10000

Release must verify ownership so you never delete a lock that someone else acquired after your lease expired:

if redis.call('GET', KEYS[1]) == ARGV[1] then
  return redis.call('DEL', KEYS[1])
else
  return 0
end

The related and often simpler pattern is an idempotency key: before processing a webhook, do SET webhook:{id} 1 NX EX 86400. If it returns nil, you already handled that event, so acknowledge and move on.

Honest caveat: a single-node Redis lock is a best-effort lock, not a consensus system. It is great for “avoid duplicate work” and poor for “guarantee financial correctness”. If money or data integrity depends on mutual exclusion, enforce it in your database with a unique constraint or SELECT ... FOR UPDATE, and treat the Redis lock as an optimisation.

Bonus in 2026: semantic caching and vector search for AI features

If your app talks to an LLM, two Redis patterns are now common. The first is a semantic cache: store embeddings of previous prompts and return the stored answer when a new question is similar enough, cutting both latency and token spend. The second is vector retrieval for RAG, where Redis acts as the index for your document embeddings alongside the metadata filters.

FT.CREATE idx:docs ON HASH PREFIX 1 doc: SCHEMA
  content TEXT
  embedding VECTOR HNSW 6 TYPE FLOAT32 DIM 1536 DISTANCE_METRIC COSINE

It will not replace a specialised vector database at very large scale, but if Redis is already in your stack it removes one moving part.

Which Redis feature fits your problem?

Your problem today Use this Key commands
Database query repeated on every page load Cache-aside with TTL GET, SET EX, DEL
Users logged out randomly behind a load balancer Session store HSET, EXPIRE
API being hammered, brute force on login Rate limiter INCR + EXPIRE, or ZSET window
WebSocket message must reach another instance Pub/Sub PUBLISH, SUBSCRIBE
Events must not be lost, need acknowledgements Streams XADD, XREADGROUP, XACK
Top N ranking or “my position” Sorted set ZADD, ZREVRANGE, ZREVRANK
Slow work blocking HTTP responses Job queue LPUSH, BLMOVE (or BullMQ, Sidekiq, Celery)
Duplicate cron runs or duplicate webhooks Lock / idempotency key SET NX PX + Lua release
redis database server

When Redis is the wrong answer

Being honest about limits is how you avoid an incident later.

  • It is not a replacement for your relational database. No joins, no rich transactional queries, and your whole working set must fit in RAM. RAM is far more expensive per gigabyte than disk.
  • Durability is configurable, not free. With RDB snapshots you can lose the last minutes of writes on a crash. AOF with appendfsync everysec narrows that to about a second. Plan around it.
  • Eviction can silently delete your data if you mix cache and non-cache workloads on the same instance with an allkeys-* policy.
  • Big single keys hurt. One command runs on one thread, so a KEYS * or a huge ZRANGE on a million-member set blocks everyone. Use SCAN and keep keys reasonably sized.
  • Low traffic apps may not need it. If your Postgres query runs in 3 ms and you serve 10 requests per minute, adding Redis adds an operational dependency for no measurable gain.

Redis vs Postgres: quick comparison

Criteria Redis PostgreSQL
Primary storage RAM, optional disk persistence Disk, with memory caching
Typical latency Sub-millisecond Low single-digit milliseconds and up
Query model Commands on known keys Full SQL with joins and aggregates
Best for Ephemeral, hot, high-churn data System of record, reporting
Verdict Complementary, not competitors. Postgres owns the truth, Redis makes access to the hot parts fast and coordinates workers.

A sensible adoption order

  1. Caching for your three slowest endpoints. Measure before and after.
  2. Sessions as soon as you scale past one app instance.
  3. Rate limiting on login, password reset, signup and any public API.
  4. Job queues when request latency is driven by work that could happen later.
  5. Pub/Sub or Streams when you add real-time UI features.
  6. Locks and idempotency keys when you run multiple schedulers or consume webhooks.
  7. Leaderboards, counters, vector search when the product asks for them.

Use separate instances (or at least separate logical databases and eviction policies) for cache-like data and cannot-lose-it data. Monitor used_memory, evicted_keys, keyspace_hits versus keyspace_misses and the slow log from day one.

FAQ

Is Redis SQL or NoSQL?

Redis is NoSQL. It is a key-value store with advanced data structures, accessed through commands rather than SQL. Redis does offer query capabilities on top (search, JSON, vector indexes), but it is not a relational engine and there is no SQL interface in the core.

Is Redis a database or a cache?

Both, depending on how you configure it. With persistence enabled (RDB snapshots, AOF, or both), replication and no eviction policy, Redis can serve as a primary database for suitable workloads such as sessions, queues or real-time state. With maxmemory plus an LRU or LFU eviction policy, it behaves as a cache. Most teams run it as a cache and coordination layer next to a relational database. Anyone digging further should read What is Redis and Why to Use Redis.

Is Redis used in frontend or backend?

Backend only. Redis is never exposed directly to browsers or mobile apps: it has no fine-grained per-user authorization model and should live on a private network. Your backend talks to Redis and serves results to the frontend.

Is Redis free to use?

Yes for the open source editions. Redis changed licensing in 2024 and then added AGPLv3 as an option with Redis 8 in 2025, so the community edition remains free to self-host under an OSI-approved license. Commercial offerings such as Redis Cloud and Redis Software are paid. The Valkey fork (BSD, Linux Foundation) is a drop-in alternative several cloud providers now ship, and most of the patterns in this post work identically on it.

What is Redis used for in Python?

The same seven use cases, through the redis-py client. Common combinations are Django or Flask caching and sessions, Celery or RQ for background jobs with Redis as the broker, and FastAPI rate limiting middleware backed by Redis counters.

What is Redis used for in Spring Boot?

Typically four things: @Cacheable method caching through Spring Cache with the Redis cache manager, Spring Session for distributed HTTP sessions, RedisTemplate for direct data structure access (counters, locks, leaderboards) and Spring Data Redis messaging for pub/sub or Streams.

Does Redis lose data when it restarts?

It depends on your persistence settings. With no persistence, everything in memory is lost. With RDB snapshots you lose writes since the last snapshot. With AOF set to everysec, you lose at most about one second of writes. For critical state, enable AOF and replication, and keep your system of record in a durable database.

How much memory do I need?

Size for your hot working set plus headroom. A rough starting point: estimate average value size times number of live keys, add 20 to 30 percent for Redis overhead and fragmentation, then set maxmemory below the machine limit so the OS never starts swapping. Verify with INFO memory and MEMORY USAGE on sample keys under real traffic.

Takeaway: “Redis is a cache” is the shortest correct answer and the least useful one. Think of Redis as a toolbox of fast, atomic data structures shared by all your servers. Match the structure to the problem in front of you, and most of the hard parts of distributed web applications become a handful of commands.

Leave a Comment

Your email address will not be published. Required fields are marked *