Laravel Caching: Tags, Stampede Locks &amp; Cache-Aside | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://www.msaied.com/public) - [Home](https://www.msaied.com/public)
- [Projects](https://www.msaied.com/public/projects)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [About](https://www.msaied.com/public#about)

           [  Contact](https://www.msaied.com/public#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://www.msaied.com/public)
- [ProjectsCase studies](https://www.msaied.com/public/projects)
- [ArticlesEngineering notes](https://www.msaied.com/public/articles)
- [CertificatesCredentials](https://www.msaied.com/public/certificates)
- [AboutHow I work](https://www.msaied.com/public#about)
- [ContactGet in touch](https://www.msaied.com/public#contact)

  [Start a conversation](https://www.msaied.com/public#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://www.msaied.com/public)
2. /
3. [Articles](https://www.msaied.com/public/articles)
4. /
5. Laravel Caching Strategies: Tags, Stampede Prevention, and Cache-Aside at Scale

 Laravel Caching Strategies: Tags, Stampede Prevention, and Cache-Aside at Scale
================================================================================

 Beyond simple Cache::get/put calls lies a set of patterns that determine whether your cache layer helps or hurts under load. This article covers tagged caches, stampede locks, and the cache-aside pattern with concrete Laravel examples.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://www.msaied.com/public#person) Published 23 Jun 2026 · Updated 23 Jun 2026 · 4 min read

ShareCopy linkCopied

 ![Laravel Caching Strategies: Tags, Stampede Prevention, and Cache-Aside at Scale](https://cdn.msaied.com/274/b30a7f822fc88219822cdfeffdb767ce.png) 

  On this page +1. [Why Basic Caching Breaks Under Load](#why-basic-caching-breaks-under-load)
2. [Tagged Caches for Precise Invalidation](#tagged-caches-for-precise-invalidation)
3. [Scoping Tags to Tenants](#scoping-tags-to-tenants)
4. [Preventing Cache Stampedes with Atomic Locks](#preventing-cache-stampedes-with-atomic-locks)
5. [The Cache-Aside Pattern with Eloquent](#the-cache-aside-pattern-with-eloquent)
6. [Probabilistic Early Expiry (XFetch)](#probabilistic-early-expiry-xfetch)
7. [Takeaways](#takeaways)

 Why Basic Caching Breaks Under Load
-----------------------------------

Most Laravel developers start with `Cache::remember()` and call it done. That works fine at low traffic, but three failure modes emerge at scale: **tag-based invalidation becomes impossible**, **cache stampedes** hammer the database when a popular key expires, and **stale reads** sneak in when you skip the cache-aside pattern. Let's fix all three.

---

Tagged Caches for Precise Invalidation
--------------------------------------

Redis and Memcached drivers support cache tags, letting you group related keys and flush them atomically without knowing every individual key.

```php
// Storing with tags
Cache::tags(['products', 'tenant:42'])->put(
    "product:{$product->id}",
    $product->toArray(),
    now()->addHour()
);

// Flush everything for tenant 42 on plan change
Cache::tags('tenant:42')->flush();

// Flush only product listings, not user data
Cache::tags('products')->flush();

```

> **Warning:** The `database` and `file` drivers do not support tags. If you switch drivers in tests, tag calls silently no-op. Pin your test environment to `redis` or use `array` with a tag-aware wrapper.

### Scoping Tags to Tenants

In a multi-tenant app, prefix every tag with the tenant identifier so a flush never bleeds across boundaries:

```php
final class TenantCache
{
    public function __construct(private readonly int $tenantId) {}

    public function tags(string ...$groups): Repository
    {
        $scoped = array_map(
            fn(string $g) => "tenant:{$this->tenantId}:{$g}",
            $groups
        );
        return Cache::tags($scoped);
    }
}

```

---

Preventing Cache Stampedes with Atomic Locks
--------------------------------------------

When a high-traffic key expires, dozens of workers race to rebuild it simultaneously. The result: a thundering herd that overwhelms your database.

Laravel's `Cache::lock()` gives you an atomic mutex:

```php
function getPopularFeed(int $userId): array
{
    $key = "feed:{$userId}";

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

    // Only one worker rebuilds; others wait up to 5 s then re-read
    return Cache::lock("lock:{$key}", seconds: 10)
        ->block(5, function () use ($key, $userId): array {
            // Double-check after acquiring the lock
            if ($cached = Cache::get($key)) {
                return $cached;
            }

            $data = FeedBuilder::for($userId)->build();
            Cache::put($key, $data, now()->addMinutes(5));
            return $data;
        });
}

```

The double-check inside the lock body is critical — without it, every queued worker rebuilds the cache after the first one finishes.

---

The Cache-Aside Pattern with Eloquent
-------------------------------------

Cache-aside means your application — not the cache — is responsible for loading on miss and writing on update. Laravel's `remember()` handles reads, but writes need explicit invalidation:

```php
final class ProductRepository
{
    private const TTL = 3600;

    public function find(int $id): Product
    {
        return Cache::tags('products')->remember(
            "product:{$id}",
            self::TTL,
            fn() => Product::with('variants')->findOrFail($id)
        );
    }

    public function save(Product $product): void
    {
        $product->save();
        // Invalidate immediately; next read repopulates
        Cache::tags('products')->forget("product:{$product->id}");
    }
}

```

Avoid writing to the cache inside `save()` — you risk a race condition where the stale write lands after a concurrent reader has already stored fresh data.

---

Probabilistic Early Expiry (XFetch)
-----------------------------------

For keys that are expensive to rebuild, you can proactively refresh before expiry using the XFetch algorithm — recompute with a probability that increases as the key ages:

```php
function xfetch(string $key, int $ttl, Closure $compute, float $beta = 1.0): mixed
{
    [$value, $expiry, $delta] = Cache::get($key) ?? [null, 0, 0];

    $now = microtime(true);
    if ($value === null || $now - $delta * $beta * log(random_int(1, PHP_INT_MAX) / PHP_INT_MAX) >= $expiry) {
        $start = microtime(true);
        $value = $compute();
        $delta = microtime(true) - $start;
        $expiry = $now + $ttl;
        Cache::put($key, [$value, $expiry, $delta], $ttl + 60);
    }

    return $value;
}

```

This is a niche tool — reach for it only when a stampede lock's blocking latency is unacceptable.

---

Takeaways
---------

- Use **cache tags** to group related keys and flush them by domain concept, not by guessing key names.
- Always **scope tags to tenants** in multi-tenant apps to prevent cross-tenant invalidation.
- Implement the **double-checked lock pattern** with `Cache::lock()->block()` to eliminate stampedes.
- Prefer **cache-aside with explicit invalidation** over writing to the cache inside save operations.
- XFetch is a last resort for extremely expensive computations where blocking is not acceptable.

- [laravel](https://www.msaied.com/public/articles?search=laravel)
- [caching](https://www.msaied.com/public/articles?search=caching)
- [performance](https://www.msaied.com/public/articles?search=performance)
- [redis](https://www.msaied.com/public/articles?search=redis)

 Frequently asked questions 
---------------------------

  Does Cache::tags() work with the file or database driver in Laravel?No. Tag support is only available on the Redis and Memcached drivers. Using tags with the file or database driver will throw a BadMethodCallException. In test environments, use the Redis driver or swap to a tag-aware in-memory store.

   What is the difference between a cache stampede lock and a regular mutex?Cache::lock() in Laravel is backed by an atomic Redis SET NX operation, making it safe across multiple workers and servers. A regular PHP mutex only protects a single process. The block() method lets waiting workers re-use the freshly populated cache value instead of each rebuilding it independently.

   When should I write to the cache inside a save method vs. just invalidating?Prefer invalidation (forget) over writing inside save. Writing risks a race where a stale value overwrites a fresher one set by a concurrent reader. Only write eagerly if cache misses are extremely expensive and you can guarantee ordering — which is rarely possible without additional coordination.

   ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)About the author
----------------

[Mohamed Said](https://www.msaied.com/public#person)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

[About](https://www.msaied.com/public#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articleLaravel API Resources: Sparse Fieldsets, Conditional Relationships, and Stable Versioning](https://www.msaied.com/public/articles/laravel-api-resources-sparse-fieldsets-conditional-relationships-and-stable-versioning) [Next articleLaravel 12: New Features, Helpers, and Practical Upgrade Notes](https://www.msaied.com/public/articles/laravel-12-new-features-helpers-and-practical-upgrade-notes)  

   On this page
-------------

1. [Why Basic Caching Breaks Under Load](#why-basic-caching-breaks-under-load)
2. [Tagged Caches for Precise Invalidation](#tagged-caches-for-precise-invalidation)
3. [Scoping Tags to Tenants](#scoping-tags-to-tenants)
4. [Preventing Cache Stampedes with Atomic Locks](#preventing-cache-stampedes-with-atomic-locks)
5. [The Cache-Aside Pattern with Eloquent](#the-cache-aside-pattern-with-eloquent)
6. [Probabilistic Early Expiry (XFetch)](#probabilistic-early-expiry-xfetch)
7. [Takeaways](#takeaways)

 ###  Have a technical challenge?

 Tell me what you’re building. I reply within two working days.

[Start a conversation](https://www.msaied.com/public#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/740/cce86edc21eddcbdd2f2454fadaf9c70.png)  · 3 min read### The Pipeline Pattern in Laravel: Custom Pipelines Beyond Middleware

5 Oct 2026 ](https://www.msaied.com/public/articles/the-pipeline-pattern-in-laravel-custom-pipelines-beyond-middleware-1) [ ![](https://cdn.msaied.com/739/2d6897fdcdcf090613f96f72a64b8a78.png)  · 4 min read### MySQL Full-Text Search in Laravel: Indexes, Relevance Scoring, and Boolean Mode

4 Oct 2026 ](https://www.msaied.com/public/articles/mysql-full-text-search-in-laravel-indexes-relevance-scoring-and-boolean-mode) [ ![](https://cdn.msaied.com/738/073696a3fefe18bec825beec5ac658f5.png)  · 4 min read### Laravel Queue Rate-Limited Middleware: Throttling Jobs Without Losing Work

4 Oct 2026 ](https://www.msaied.com/public/articles/laravel-queue-rate-limited-middleware-throttling-jobs-without-losing-work) 

  Have a technical challenge?
----------------------------

Tell me what you’re building. I reply within two working days.

 [Discuss your project ↗](https://www.msaied.com/public#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://www.msaied.com/public)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://www.msaied.com/public/sitemap.xml)
