Cache-Aside Pattern with Redis: Performance Without Losing Data Ownership

How I use Redis cache-aside safely, including TTL, cache stampede, invalidation, stale-data semantics, failure fallback and the conditions where I do not add a cache.

Romharshan Singh
Romharshan SinghSenior Solution Architect • AI & Cloud Mentor
1 September 20263 min read0 viewsUpdated 1 Sept 2026
Cache-Aside Pattern with Redis: Performance Without Losing Data Ownership

Cache-Aside Pattern with Redis: Performance Without Losing Data Ownership

Why I add Redis

I add a cache after identifying a measurable read hotspot.

I do not start with:

every service should use Redis.

Cache-aside flow:

plaintext
GET cache key
   | hit -> return
   |
 miss
   v
source of truth
   |
   v
SET cache + TTL
   |
 return

NestJS example

typescript
async getProduct(
  productId: string,
) {
  const key =
    `product:${productId}`;

  const cached =
    await this.redis.get(key);

  if (cached) {
    return JSON.parse(cached);
  }

  const product =
    await this.productRepo
      .findById(productId);

  if (!product) {
    throw new NotFoundException();
  }

  const ttlSeconds =
    300 + Math.floor(
      Math.random() * 30,
    );

  await this.redis.set(
    key,
    JSON.stringify(product),
    'EX',
    ttlSeconds,
  );

  return product;
}

TTL jitter prevents many keys created at the same time from expiring simultaneously.

Invalidation

After update:

typescript
await db.transaction(
  async (tx) => {
    await tx.product.update(
      productId,
      changes,
    );
  },
);

await redis.del(
  `product:${productId}`,
);

There is a small failure window between DB commit and cache delete.

For high-consistency use cases, I use event-driven invalidation/versioning or avoid caching the field.

Cache stampede

A popular key expires.

10,000 requests miss.

All hit database.

Protection:

typescript
const lock =
  await redis.set(
    `lock:${key}`,
    requestId,
    'NX',
    'EX',
    10,
  );

if (lock) {
  try {
    return await refresh(key);
  } finally {
    await releaseLockSafely(
      key,
      requestId,
    );
  }
}

await sleep(50);
return readAgain(key);

In production I use a safe compare-and-delete lock release or a library rather than deleting another request's lock accidentally.

Redis outage

My policy depends on source capacity.

For reference-data cache:

plaintext
Redis down -> read DB

For a cache protecting an already maxed DB:

plaintext
Redis down -> degrade/rate limit

A cache is not free availability.

What I do not cache

  • authorization decisions without careful invalidation;
  • rapidly changing transactional state;
  • huge payloads with low reuse;
  • data already fast enough in DB;
  • errors for long TTL by accident.

Production checklist

  • Define source of truth.
  • Measure current bottleneck.
  • Define acceptable staleness.
  • Add TTL jitter.
  • Protect hot misses.
  • Monitor hit ratio.
  • Monitor Redis latency.
  • Define Redis-outage behavior.
  • Size memory/eviction policy.
  • Test invalidation.

FAQ

Is cache-aside eventually consistent?

Yes, unless invalidation/version techniques provide stronger semantics.

What hit ratio is good?

It depends on workload. A high hit ratio is not useful if the cached data is cheap or stale incorrectly.

Should Redis be shared by many services?

It can be, but keys, memory quotas and failure isolation need clear ownership.

- [Microservices Design Patterns I Have Used in Real Enterprise Projects](/articles/microservices-design-patterns-real-enterprise-projects) - [Database per Service](/articles/database-per-service-data-ownership-microservices)
  • Backpressure

  • Backend Engineering

    Architect's final take

    The cache should remain an optimization. When business correctness starts depending on stale cache state, the architecture has crossed an important boundary.

Was this article useful?

Your feedback helps prioritize deeper technical content.

Romharshan Singh
ABOUT THE AUTHOR

Romharshan Singh

Senior Solution Architect and Full Stack Technology Leader with 20+ years of enterprise engineering experience across AI, cloud, distributed systems, Java, Node.js, React, Angular, Kafka and Kubernetes.