Redis: When a Small App Actually Needs It (and When Postgres Is Enough)
Redis shows up in every architecture diagram, and AI tools add it by reflex. It's excellent at a few specific jobs — caching, rate limiting, ephemeral state, pub/sub — and unnecessary for many small apps. What it's for, what it isn't, and how to use it without losing data you cared about.
Ask an AI tool to make an app "production-ready" and there's a good chance Redis appears: a cache here, a session store there, a job queue. Redis is genuinely excellent software. It's also one more service to run, secure, pay for, and understand — and for many small apps, Postgres already does the job well enough.
Here's how to tell the difference.
What Redis is
Redis is an in-memory data store. It keeps data in RAM, which makes reads and writes extremely fast — typically well under a millisecond. It stores data under keys, with rich data types: strings, hashes, lists, sets, sorted sets, streams, and more. Keys can expire automatically after a set time.
(Redis changed its licence in 2024, and the Linux Foundation's Valkey fork emerged as an open-source, compatible alternative; Redis later added an open-source licence option again. Many managed providers offer one or both. For your application code, they're effectively interchangeable.)
What Redis is great at
1. Caching
Store the result of an expensive computation or slow query, with an expiry:
const key = `product:${id}`
let product = await redis.get(key)
if (!product) {
product = JSON.stringify(await db.getProduct(id))
await redis.set(key, product, { EX: 300 }) // cache for 5 minutes
}
Good when the same expensive result is requested often and slightly stale data is acceptable.
2. Rate limiting
Counting requests per user per minute, with counters that expire on their own, is a textbook Redis job — fast, atomic increments shared across all your app instances. (How to Stop Bots From Running Up Your AI App's Bill.)
3. Ephemeral, high-churn state
Presence ("who's online"), short-lived tokens, typing indicators, temporary locks. Data that changes constantly and doesn't need to survive forever.
4. Pub/sub and real-time fan-out
Broadcasting a message to every app instance — for example, so a websocket server on instance A can notify users connected to instance B.
5. Leaderboards and counters
Sorted sets make "top 100 scores" or "most viewed this hour" trivial and fast.
6. Job queues (sometimes)
Popular queue libraries — BullMQ (Node), Sidekiq (Ruby), Celery (Python) — use Redis. They're mature and fast.
When Postgres is enough
Here's the part AI tools skip. For a small or medium app, Postgres can often do these jobs well enough, with one less service:
- Job queues — Postgres-backed libraries (pg-boss, Graphile Worker, Solid Queue, Oban, Procrastinate) are reliable and let you enqueue a job in the same transaction as the data it concerns. (Your App Needs Background Jobs.)
- Sessions — a sessions table works fine at small scale; many auth libraries support it directly.
- Caching — often the real fix for a slow query is an index, not a cache. (Database Indexes: Why Your App Got Slow.) And frameworks have their own caches.
- Rate limiting — a simple counter table works at modest volume.
- Pub/sub — Postgres has
LISTEN/NOTIFYfor lightweight notifications.
The honest rule of thumb: add Redis when you have a specific need that Postgres handles badly — not because a diagram had it. Signs you've reached that point: very high request rates on the same keys, rate limiting across many instances under load, real-time fan-out, or a queue library you specifically want that requires it.
The catch: it's memory, and it can forget
Redis's speed comes from keeping data in RAM. That has consequences:
- Persistence is configurable, not guaranteed. Redis can write snapshots (RDB) and/or an append-only log (AOF) to disk, but depending on settings, a crash can lose recent writes. Many managed caches run with persistence off.
- Memory is finite. When Redis fills up, it either rejects writes or evicts keys according to its
maxmemory-policy. For a cache, evicting old keys is exactly right. For a job queue, silently evicting jobs is a disaster.
So: treat Redis as a cache unless you've deliberately configured it otherwise. Anything you can't afford to lose — orders, payments, user data — belongs in Postgres. If you use Redis for queues, use a noeviction policy and persistence, and monitor memory.
Caching without regret
Caches introduce a classic problem: stale data. A few habits:
- Always set an expiry (
EX). A cache without expiry is a slow memory leak. - Invalidate on write — when a product is updated, delete its cache key.
- Namespace keys —
product:42,user:7:settings— so you can reason about and clear them. - Never cache per-user data under a shared key. A cache key that doesn't include the user ID can serve one user's data to another.
- Measure first. Cache what's actually slow and frequently requested, not everything.
Security basics
- Never expose Redis to the internet. It should only be reachable from your app's network. Exposed Redis instances are routinely found and abused.
- Require authentication (a password or ACL user) and use TLS where the provider supports it.
- Keep the connection string in an environment variable (commonly
REDIS_URL). (What Is an Environment Variable?)
A quick decision guide
| Need | Start with |
|---|---|
| Background jobs | Postgres-backed queue |
| Sessions | Postgres (or your auth provider) |
| Slow query | An index, then a cache if still needed |
| Rate limiting across many instances | Redis |
| Real-time fan-out / presence | Redis |
| Leaderboards, high-rate counters | Redis |
| Anything you can't lose | Postgres |
EasySpawn provisions Redis alongside Postgres on the Team plan, delivered to your app as REDIS_URL on a private network — so when you do need it, it's one setting rather than one more service to run. See pricing or join the waitlist.
Related: Postgres Connection Pooling Explained · Which Database Should an AI-Built App Use? · What Is Caching? · Preventing Cache Stampedes
Keep reading
Database Indexes: Why Your App Got Slow and How to Fix It
The app was fast with 100 rows and crawls with 100,000. The fix is usually an index. How indexes work, how to find the slow queries, how to read EXPLAIN ANALYZE, which columns to index (including the foreign keys ORMs forget), and what indexes cost.
The Transactional Outbox Pattern: Reliable Events Without Dual Writes
Writing to your database and publishing an event can't be made atomic, so one eventually happens without the other. How the transactional outbox fixes it: polling relays vs CDC, ordering, at-least-once delivery, idempotent consumers with an inbox, cleanup, and monitoring.