Back to Blog
Guide11 min read2026-07-07

How to Set Up Redis Caching for Your App

Redis can turn a slow endpoint into an instant one, if you cache the right things and invalidate correctly. This guide covers cache-aside, TTLs, key design, stampede protection, and the failure modes that bite teams.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

What caching actually buys you

A cache trades a little staleness for a lot of speed. Instead of hitting your database (or an expensive computation, or a slow upstream API) on every request, you store the result in fast in-memory storage and serve subsequent requests from there. Redis is the default choice: in-memory, microsecond-fast, and rich with data structures beyond plain key-value.

The hard part of caching isn't reading, it's knowing *what* to cache and *when to throw it away*.

The cache-aside pattern

The most common and robust pattern is cache-aside (lazy loading): the application checks the cache first, and on a miss, loads from the source and populates the cache.

async function getUser(id) {
  const key = `user:${id}`;
  const cached = await redis.get(key);
  if (cached) return JSON.parse(cached);          // cache hit

  const user = await db.getUser(id);              // cache miss -> source
  await redis.set(key, JSON.stringify(user), 'EX', 300);  // populate, 5 min TTL
  return user;
}

The app owns the cache logic, which keeps the cache from becoming a single point of failure: if Redis is down, you fall back to the database (degraded but working).

Always set a TTL

The simplest, most effective invalidation strategy is a time-to-live. Every cached entry expires after some window, bounding how stale data can get without any manual invalidation. Choose TTLs by how tolerant the data is of staleness:

DataSuggested TTLRationale
User profile5-15 minChanges occasionally
Product catalog1-24 hoursRarely changes
Pricing / inventorySeconds to 1 minMust be fresh
Computed dashboard1-5 minExpensive, tolerates lag

When in doubt, start with a short TTL. It's the safest form of invalidation.

Active invalidation on writes

TTLs bound staleness; for data that must update immediately after a write, also invalidate (or update) the cache on mutation:

async function updateUser(id, changes) {
  const user = await db.updateUser(id, changes);
  await redis.del(`user:${id}`);   // invalidate; next read repopulates
  return user;
}

Combine TTL (safety net) with write-time invalidation (freshness). Relying on invalidation alone is fragile, miss one write path and you serve stale data forever.

Key design matters

Good keys are descriptive and namespaced so you can reason about and selectively clear them:

user:1234                 # a single user
user:1234:orders          # related collection
product:567:v2            # include a version to bust caches on schema change
org:42:dashboard:summary  # tenant-scoped

Namespacing by tenant/org is essential in multi-tenant apps, you never want one tenant's cached data leaking to another.

The cache stampede problem

Here's a failure mode that bites at scale: a popular key expires, and suddenly hundreds of concurrent requests all miss the cache and hammer the database at once, a stampede (or "thundering herd"). Mitigations:

  • Locking / single-flight: the first request to miss acquires a short lock, fetches, and populates; others wait briefly and read the fresh value.
  • Stale-while-revalidate: serve the slightly-stale value while one request refreshes in the background.
  • Jittered TTLs: add randomness to expiry so keys don't all expire simultaneously.
// Jittered TTL avoids synchronized expiry
const ttl = 300 + Math.floor(Math.random() * 60);  // 300-360s
await redis.set(key, value, 'EX', ttl);

What not to cache

  • Highly volatile, must-be-exact data (e.g., a bank balance you're about to debit).
  • Per-request unique data that's never reused, no hit rate, just overhead.
  • Sensitive data without thinking through expiry and access, a cache is still a data store.

Measure your hit rate. A cache with a 10% hit rate is mostly adding latency and complexity. Aim high; if you can't, you're probably caching the wrong things.

Beyond key-value: Redis data structures

Redis is more than GET/SET. Useful patterns:

  • Rate limiting with INCR + EXPIRE.
  • Leaderboards with sorted sets (ZADD/ZRANGE).
  • Session storage with hashes and TTLs.
  • Simple queues with lists (though dedicated queues are better for real workloads).

Running Redis with PandaStack

You can run a managed Redis instead of operating one yourself. PandaStack offers managed Redis (via KubeBlocks on GKE) alongside PostgreSQL, MySQL, and MongoDB. Add it to your project and connect your app via the provided connection details. Since PandaStack auto-wires managed databases by injecting connection info into the service environment, your app reads the connection from env vars rather than hardcoded hosts.

// App connects to managed Redis via an injected/configured env var
const redis = new Redis(process.env.REDIS_URL);

A common architecture on PandaStack: a container web service for your app, a managed Postgres for primary data (auto-wired DATABASE_URL), and a managed Redis for caching and sessions, all in one project with one bill. Note that free-tier databases are sized for dev/hobby use, so for production cache workloads you'll want a paid plan with more memory and connections.

Conclusion

Redis caching is high-leverage when done with discipline: use cache-aside, always set a TTL, invalidate on writes, namespace your keys, and protect against stampedes with jitter or single-flight. Then measure your hit rate and cache the things that actually repeat. Done right, it's the cheapest big latency win available.

PandaStack's managed Redis lets you add a cache layer without operating it, right next to your app and database. Spin one up on the free tier at https://dashboard.pandastack.io.

References

  • Redis documentation: https://redis.io/docs/latest/
  • Redis key expiration (TTL): https://redis.io/docs/latest/develop/use/keyspace/
  • Cache-aside pattern (Microsoft): https://learn.microsoft.com/en-us/azure/architecture/patterns/cache-aside
  • Redis data types: https://redis.io/docs/latest/develop/data-types/

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Guide

Browse all Guide articles →

See also