Bablu Kumar Singh
Back to Blog
Backend Development
4 min read
May 10, 2026

Redis Caching Strategies for Backend Developers

Redis Caching Strategies for Backend Developers

If the same query runs on every request and the answer barely changes, that's compute you're paying for twice. Redis sits in front of the database for exactly this case — here's how I actually use it, past just SET and GET.

Why Redis?

Redis stores data in RAM. Reads happen in O(1) for simple keys and O(log N) for sorted sets. It supports strings, hashes, lists, sets, sorted sets, and streams — making it far more versatile than a simple key-value cache.

Cache-Aside (Lazy Loading)

The most common pattern. The application checks Redis first; on a miss it reads from the database, writes the result to Redis, and returns:

import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL);

async function getUserById(userId: string) {
  const cacheKey = `user:${userId}`;

  // 1. Check cache
  const cached = await redis.get(cacheKey);
  if (cached) return JSON.parse(cached);

  // 2. Cache miss — query the database
  const user = await User.findById(userId).lean();
  if (!user) return null;

  // 3. Populate cache with a TTL (e.g. 10 minutes)
  await redis.set(cacheKey, JSON.stringify(user), 'EX', 600);

  return user;
}

Pros: Only requested data enters the cache; cold-start is graceful. Cons: The first request for every key is always slow (cache miss).

Write-Through Caching

In write-through, you update the cache at the same time you write to the database. This ensures the cache is always warm and fresh:

async function updateUser(userId: string, updates: Partial<IUser>) {
  // 1. Write to database
  const user = await User.findByIdAndUpdate(userId, updates, { new: true }).lean();

  // 2. Immediately update cache
  const cacheKey = `user:${userId}`;
  await redis.set(cacheKey, JSON.stringify(user), 'EX', 600);

  return user;
}

This eliminates stale reads after mutations, but adds a write to Redis on every update — acceptable for entities that are read far more often than they are written.

Cache Invalidation

The two hardest problems in computer science: cache invalidation, naming things, and off-by-one errors. Strategies I use:

Time-Based Expiry (TTL)

Set a TTL on every key. Even if invalidation logic has a bug, stale data expires automatically:

await redis.set('settings:global', JSON.stringify(settings), 'EX', 3600);

Event-Driven Invalidation

When a mutation occurs, explicitly delete the affected keys:

async function deleteUser(userId: string) {
  await User.findByIdAndDelete(userId);
  await redis.del(`user:${userId}`);
}

Pattern-Based Invalidation

For clearing all keys matching a pattern (e.g., all dashboard data for a tenant):

async function invalidateTenantCache(tenantId: string) {
  const keys = await redis.keys(`tenant:${tenantId}:*`);
  if (keys.length > 0) {
    await redis.del(...keys);
  }
}

Warning: KEYS blocks the Redis event loop on large datasets. In production, use SCAN with a cursor instead.

Distributed Locking with Redlock

When multiple pods process the same job, you need a distributed lock to prevent double-processing. The Redlock algorithm provides a safe mutual exclusion primitive:

import Redlock from 'redlock';

const redlock = new Redlock([redis], {
  retryCount: 3,
  retryDelay: 200,
});

async function processPayment(paymentId: string) {
  const lock = await redlock.acquire([`lock:payment:${paymentId}`], 5000);
  try {
    // Critical section — only one pod executes this
    const payment = await Payment.findById(paymentId);
    if (payment.status !== 'pending') return;

    await chargeStripe(payment);
    payment.status = 'completed';
    await payment.save();
  } finally {
    await lock.release();
  }
}

Session Storage

Storing Express sessions in Redis makes your API stateless across pods:

import session from 'express-session';
import RedisStore from 'connect-redis';

app.use(
  session({
    store: new RedisStore({ client: redis }),
    secret: process.env.SESSION_SECRET!,
    resave: false,
    saveUninitialized: false,
    cookie: { maxAge: 86400000 }, // 1 day
  })
);

Monitoring Cache Health

Track these metrics to avoid cache-related outages:

  • Hit rate — should stay above 90 %. A low hit rate means your TTLs are too short or keys are being evicted prematurely.
  • Memory usage — set maxmemory and a maxmemory-policy (e.g., allkeys-lru) to prevent Redis from consuming all available RAM.
  • Eviction count — rising evictions indicate you need more memory or smarter TTLs.

Wrapping Up

Cache-aside with a TTL covers most cases — start there before reaching for write-through or distributed locks. And keep an eye on hit rate; a cache nobody's hitting is just extra infrastructure to maintain.

#Redis#Caching#Performance#Node.js
Bablu Kumar Singh
Written by

Bablu Kumar Singh

Backend-Focused Full Stack Developer

Writes about backend systems and databases based on what comes up while building with Node.js, MongoDB, PostgreSQL, and Redis.

You May Also Like

Designing a Role-Based Access Control (RBAC) System from Scratch
Backend Development
2 min read

Designing a Role-Based Access Control (RBAC) System from Scratch

A blueprint on implementing hierarchical permission matrices in Express.js middleware using bitwise operations and MongoDB caching.

May 24, 2026Read
Authentication Systems in Node.js: JWT, Refresh Tokens, and Security
Backend Development
4 min read

Authentication Systems in Node.js: JWT, Refresh Tokens, and Security

Build a production-grade authentication system in Node.js — covering JWT access tokens, refresh token rotation, password hashing with bcrypt, and common security pitfalls to avoid.

May 28, 2026Read
Building SaaS Platforms with Node.js: Architecture to Payment Integration
Backend Development
4 min read

Building SaaS Platforms with Node.js: Architecture to Payment Integration

End-to-end guide for building multi-tenant SaaS applications with Node.js — covering tenant isolation, subscription billing with Stripe, webhook handling, and production deployment.

May 19, 2026Read