t4mer@notebook

// article · redis · laravel · 11 march 2026 · 2 min

How I think about Redis caching in Laravel applications

Faster is easy. Wrong and fast is the expensive version.

Redis in a Laravel app is usually introduced as a performance win. Put this query in the cache. Remember this config. Session driver, why not.

Then one day the cache contains a decision that is no longer true, or five hundred workers stampede the database because a key expired, and Redis is suddenly a correctness problem wearing a speed costume.

This is how I think about it now, after enough of those days.

Cache aside is the default, and that is fine

Laravel’s Cache::remember is cache-aside:

  1. Look up the key
  2. If missing, run the callback
  3. Store the result

It is easy to reason about. The database remains the source of truth. Redis is a hint.

The hint can be stale. That is the contract. If you cannot tolerate stale, you do not want a TTL cache. You want the value to be invalidated when the write happens, or you want to not cache it.

Invalidation is the actual design

I treat TTL as a safety net, not as a strategy.

If a user updates a setting and the next request still shows the old setting for 30 minutes, the cache is not “working as designed”. It is lying.

The rule I try to keep:

  • Writes that users can see must invalidate the keys those pages read.
  • TTL exists for keys you forgot, for process crashes, for the day invalidation misses.

Naming keys carefully is part of invalidation. user:42:nav is invalidatable. page:dashboard is a junk drawer.

Stampede

Cache::remember does not, by itself, prevent a thundering herd. When a hot key expires, every worker can miss at once and every worker can recompute.

For most keys this does not matter. For a report query that takes two seconds and is on the homepage, it does.

Options I have used, in increasing annoyance:

  • a slightly longer TTL than you think you need
  • locking around rebuild (Cache::lock)
  • precomputing into a known key from a scheduled job so the request path almost never rebuilds

I do not start with the clever option. I start with “is this key actually hot?”

What I refuse to cache

  • anything that is a permission check unless I am very sure about invalidation
  • unpaid / paid state that gates a feature
  • “temporary” values that became load-bearing

Redis will store a bad idea with excellent latency.

Redis is also not only a cache

Queues, locks, rate limiters, sessions. Those are different contracts. A memory eviction policy that is acceptable for a cache is not acceptable for a lock. Mixing them on one instance without thinking about maxmemory-policy is how you get a mysteriously lost lock at 3am.

I like Redis a lot. I like it more when each key has a sentence attached: this may be stale, or this must not be evicted, or this is a queue, not a cache.