Cache Versioning in Laravel - Stop Deleting Cache, Start Ignoring It

Instead of manually clearing cache, Laravel developers can use versioned keys to invalidate data instantly and safely. This simple pattern eliminates race conditions and simplifies caching logic.

April 3, 2026 · Laravel
Cache Versioning in Laravel - Stop Deleting Cache, Start Ignoring It featured image

Cache Versioning in Laravel: Stop Deleting Cache, Start Ignoring It

Caching in Laravel feels great in the beginning. You wrap things with Cache::remember, your app gets faster, and everything looks clean. But the moment your data changes and stale cache sticks around, things start getting messy.

You try to fix it by calling Cache::forget, maybe in a controller, maybe in a model event, and slowly your codebase fills up with scattered invalidation logic. It works… until it doesn’t. You miss one place, and suddenly users are seeing outdated data.

There’s a much simpler way to think about this problem. Instead of trying to delete cache, you can make it irrelevant. That’s exactly what cache versioning does.

The Core Idea: Version Your Cache Keys

Normally, you might store cache like this:

$key = "user:{$userId}";

With versioning, you attach a version to the key:

$key = "user:{$userId}:v1";

When the user updates, instead of deleting the cache, you just move to:

$key = "user:{$userId}:v2";

Now your app reads from a completely new key. The old cache still exists, but nothing uses it anymore.

Making It Practical in Laravel

Hardcoding versions like v1, v2 is not realistic. So the version should live in cache (or Redis) and be dynamically fetched.

Step 1: Create a Helper

function versioned_cache_key(string $baseKey, string $versionKey): string
{
    $version = Cache::get($versionKey, 1);
    return "{$baseKey}:v{$version}";
}

Step 2: Read from Cache

function getUser($userId)
{
    $versionKey = "user:{$userId}:version";
    $key = versioned_cache_key("user:{$userId}", $versionKey);

    return Cache::remember($key, 3600, function () use ($userId) {
        return \App\Models\User::find($userId);
    });
}

Step 3: Invalidate Cache

function invalidateUser($userId)
{
    Cache::increment("user:{$userId}:version");
}

That’s it. No Cache::forget, no chasing keys across your app.

A Cleaner Approach: Use Model Events

Instead of manually calling invalidation everywhere, hook into Laravel model events.

use App\Models\User;

User::updated(function ($user) {
    Cache::increment("user:{$user->id}:version");
});

Now cache invalidation happens automatically whenever the model changes.

Why This Works So Well

One big advantage is that you no longer need to track cache keys. You don’t care how many keys exist or where they are used. The version controls everything in a predictable way.

It also gives you instant invalidation. As soon as the version increases, all future reads use a new key. Old cache entries are effectively invisible.

This becomes even more valuable when your app scales. With multiple servers or workers, deleting cache can lead to race conditions or inconsistencies. Versioning avoids that because every instance reads the same version value.

Avoiding Race Conditions

Let’s say two requests happen at the same time. One invalidates cache, another writes stale data.

With traditional invalidation, stale data might overwrite fresh cache. With versioning, that stale write goes to the old key and never affects the current version.

// Old version
user:42:v1

// New version
user:42:v2

Your system naturally protects itself.

Global Cache Versioning

Sometimes you want to invalidate everything, like after a deployment.

function global_cache_key($key)
{
    $version = Cache::get('global:version', 1);
    return "{$key}:v{$version}";
}

Invalidate everything:

Cache::increment('global:version');

Now all cache keys shift automatically.

Composite Versioning (Advanced)

You can also combine multiple versions for more control.

$key = "feed:{$userId}:u{$userVersion}:p{$postVersion}";

Now the cache updates when either:

This gives you precise invalidation without complicated logic.

What About Old Cache?

Old cache entries will still exist for some time, but they will expire based on TTL. In most cases, this is perfectly fine.

You’re trading a bit of memory for a much simpler and safer system, which is almost always worth it.

The Mental Shift

Instead of asking:

“How do I delete this cache?”

Start asking:

“How do I make this cache irrelevant?”

That small shift removes a lot of complexity from your application.

Isn't it fun?

Cache versioning fits Laravel beautifully because it works with the framework instead of fighting it. You keep using Cache::remember, but you remove the hardest part - manual invalidation.

Once you start using this pattern, cache invalidation stops being a problem you constantly think about. It just becomes part of the system, quietly doing its job in the background.

#Laravel #Performance #Tips & Tricks #Developer Productivity
Hasin Hayder

Written by

Hasin Hayder

Writing about code, design, and building things

More from Hasin Hayder

Unlocking Model Reasoning in GitHub Copilot

Most developers change models. Smart developers change how the model thinks. Learn how reasoning levels in GitHub Copilot can dramatically improve code quality - even with smaller models.

Apr 3, 2026 · 3 min read

Give yourself some credit too!

You’ve done more than you think - but you rarely stop to notice. In a world obsessed with the next goal, this is a reminder to pause, reflect, and give yourself credit for how far you’ve already come.

Apr 1, 2026 · 3 min read
Back to all articles

Comments

Be the first to leave a comment.

Leave a Comment

Your comment will be held for moderation before appearing.