Skip to main content
David Dew Mallick

David Dew Mallick

ProjectsExperienceSkillsBlog
Back to blog
laravel cache tagslaravel redis cachecache stampede laravellaravel cache remember

Laravel Caching Strategies That Cut DB Load

Laravel caching strategies that cut database load: Redis drivers, cache tags, atomic locks, and stampede protection with real code.

Sep 19, 2026 · 8 min read

Laravel caching is the cheapest way to remove load from PostgreSQL, but most teams only ever call Cache::remember() and hope. This guide covers the driver choice, key design, tag-based invalidation, atomic locks, and stampede protection that hold up under concurrent traffic. In production systems I've tuned, the cache layer — not the query — decided whether a page stayed under 200 ms.

The short version

Use a shared Laravel redis cache for both values and locks, namespace every key by tenant and version, invalidate with tags instead of guessing key names, and wrap expensive reads in an atomic lock so a cold key doesn't trigger a thousand identical queries.

Which cache driver should Laravel caching use in production?

The file driver is fine for local development and terrible for anything with more than one app server. Each node gets its own copy, so invalidation only clears the node that handled the write. The database driver centralizes state but puts cache reads back on the same database you were trying to protect.

A Laravel redis cache wins for most deployments because it gives you a shared store, TTL at the key level, tag support, and the atomic locks that Cache::lock() depends on. Configure it once in config/database.php and point the cache store at it:

// config/cache.php
'default' => env('CACHE_STORE', 'redis'),

'stores' => [
    'redis' => [
        'driver' => 'redis',
        'connection' => 'cache',
        'lock_connection' => 'default',
    ],
],

Keep the cache connection separate from the queue connection. A cache flush should never evict queued jobs, and a queue backlog should never push cached pages out of memory. Two logical Redis databases, two maxmemory-policy settings: allkeys-lru for cache, noeviction for queues.

Driver

Shared across nodes

Tags

Atomic locks

Best fit

file

No

No

No

Local dev only

database

Yes

No

No

Single small app

memcached

Yes

No

No

Pure key/value reads

redis

Yes

Yes

Yes

Most Laravel apps

How do you design cache keys that survive refactors?

A cache key is a public interface. If it is assembled inline at ten call sites, changing the shape of a returned model breaks nine of them silently. Centralize key generation in a small class or a method on the model itself.

final class CacheKeys
{
    public static function tenant(int $tenantId): string
    {
        return "t{$tenantId}";
    }

    public static function dashboard(int $tenantId, int $userId): string
    {
        return self::tenant($tenantId) . ":dashboard:u{$userId}";
    }
}

Prefix every key with the tenant identifier so a single tenant's data can be purged without touching anyone else. Add a version segment when the payload shape changes — v2 in the key is cheaper than a coordinated flush across every node. Finally, keep keys short: Redis stores the full key string per entry, and long keys add up across millions of entries.

Never interpolate raw user input into a cache key. Hash it first with hash('xxh128', $input) so a crafted string can't collide with another tenant's key or blow past Redis key-length limits.

Laravel cache tags: invalidation without key archaeology

TTL-based expiry alone forces you to choose between stale data and short hit windows. Laravel cache tags let you group related entries and flush them as a unit when the underlying rows change.

use Illuminate\Support\Facades\Cache;

$posts = Cache::tags(['tenant:'.$tenantId, 'posts'])
    ->remember("posts:list:{$tenantId}", now()->addMinutes(10), function () use ($tenantId) {
        return Post::where('tenant_id', $tenantId)
            ->latest('published_at')
            ->limit(50)
            ->get();
    });

// On write, drop only what changed.
Cache::tags(['tenant:'.$tenantId, 'posts'])->flush();

Tags work with Redis and Memcached, not with file or database. That is another reason the driver choice comes first. Keep the tag vocabulary small and stable — tenant:{id} plus an entity name covers most apps — because every tag you add is another index Redis has to maintain on write.

How do you stop cache stampede in Laravel?

The failure mode that bites hardest: a popular key expires, hundreds of concurrent requests miss simultaneously, and every one of them runs the same expensive query. The database sees a spike exactly when it is least able to absorb it. This is the classic cache stampede Laravel applications hit on dashboards and report pages.

Two defenses. First, stagger expiry with a random jitter so keys created together do not die together. Second, wrap the recomputation in an atomic lock so only one worker rebuilds the value while the rest wait briefly and read the fresh result.

use Illuminate\Support\Facades\Cache;

function cachedReport(int $tenantId): array
{
    $key = "tenant:{$tenantId}:report:v3";

    if ($cached = Cache::get($key)) {
        return $cached;
    }

    $lock = Cache::lock("{$key}:lock", 10);

    try {
        $lock->block(5);

        return Cache::remember($key, now()->addMinutes(15)->addSeconds(random_int(0, 60)), function () use ($tenantId) {
            return buildExpensiveReport($tenantId);
        });
    } catch (\Illuminate\Contracts\Lock\LockTimeoutException $e) {
        return Cache::get($key, fn () => buildExpensiveReport($tenantId));
    } finally {
        optional($lock)->release();
    }
}

The block(5) call makes waiting workers sleep rather than hammer the database. The fallback in the catch block keeps the endpoint responsive if the lock holder dies mid-rebuild. Ten seconds of lock TTL is enough for most report queries; raise it only when you can prove the query needs it.

What should never go in the cache?

Cache the result of a read, not the state of a write. Anything that must be transactionally consistent — account balances, inventory counts, permission checks that gate a mutation — belongs in PostgreSQL with the right indexes, not in Redis with a TTL.

Also avoid caching entire authenticated page payloads keyed only by URL. Session-scoped data leaks across users the moment the key omits the user or tenant. If you must cache fragments, include the authorization scope in the key and test the miss path in CI.

Data

Cache it?

Why

Public listing pages

Yes, with tags

Read-heavy, tolerant of seconds of staleness

Aggregate dashboards

Yes, with locks

Expensive query, many concurrent readers

Account balances

No

Correctness beats latency

Permission checks

Short TTL only

Revocation must propagate quickly

Instrument hit rate before tuning TTLs. A low hit rate usually means keys are too specific, not that the TTL is too short. Log Cache::get() misses in staging and count them per key prefix.

Monitoring and eviction in production

Set maxmemory on the Redis instance and choose the policy deliberately. Under allkeys-lru, Redis evicts the least recently used key when memory fills — fine for a pure cache, catastrophic if queue jobs or sessions share the same instance. Watch evicted_keys and keyspace_hits in your metrics; a rising eviction count with a flat hit rate means the working set no longer fits.

For queue-heavy apps, keep the existing Laravel queue best practices in mind and isolate the cache connection, as shown earlier. If your cache misses are actually query problems, the fixes in Eloquent N+1 in Laravel apply before any caching layer. Background reading on key eviction behaviour lives in the Redis eviction documentation, and the full API surface is in the Laravel cache documentation.

Is Cache::remember always the right call?

No. Laravel cache remember is the default for a reason — it reads, computes on miss, and writes in one call — but it hides the miss path. When a key is expensive and popular, that hidden miss is exactly where stampede protection belongs, so reach for an explicit lock instead.

Use remember for cheap, low-contention reads: config lookups, small reference tables, single-row fetches. Use the lock pattern for anything that scans, aggregates, or fans out to another service. Mixing both in one app is normal; the mistake is using remember everywhere and discovering the miss cost during a traffic spike.

FAQ

Does Laravel caching work with multiple app servers?

Only with a shared driver. Redis and Memcached work across nodes; the file driver keeps a separate cache per server, so invalidation on one node leaves the others stale.

Are Laravel cache tags supported on every driver?

No. Tags require Redis or Memcached. The file and database drivers throw a BadMethodCallException when you call tags(), so pick the driver before designing invalidation.

How do I prevent cache stampede in Laravel?

Combine random TTL jitter with Cache::lock() around the recomputation. One worker rebuilds the value while others block briefly, then read the fresh entry.

What TTL should I use for cached queries?

Start at five to fifteen minutes for read-heavy listings, then tune from measured hit rate and staleness tolerance. There is no universal number; the key design matters more than the TTL.


Treat the cache as a system with its own failure modes, not a decorator you sprinkle on queries. Choose a Laravel redis cache, namespace keys by tenant, invalidate with Laravel cache tags, and guard cold keys with locks. For a broader set of conventions, see the other Laravel guides on the blog or the projects listed at /#projects.

DD

David Dew Mallick

Software Engineer

I build AI-driven SaaS infrastructure and backend systems with Laravel, AWS, and SQL, and write about the engineering decisions behind them.

GitHubLinkedInEmail

More posts

  • Sep 22, 2026 · 8 min read

    Laravel Middleware: A Practical Guide to Request Flow

    Laravel middleware controls every request your app handles. Learn to write, register, and order middleware for auth, throttling, and request mutation.

  • Sep 16, 2026 · 6 min read

    Laravel Validation: Rules, Custom Checks & Best Practices

    Master Laravel validation with custom rules, form requests, and real-time techniques to keep bad data out of your PostgreSQL database and APIs for good.

End of record

Back to top↑

David Dew Mallick

Dhaka, Bangladesh

Contact

  • david.dew.mallick@g.bracu.ac.bd
  • GitHub
  • LinkedIn

2026 David Dew Mallick