Performance & Caching

Laravel Caching in Practice: What to Cache, TTLs and Invalidation

A practical guide to caching in Laravel: picking the right data to cache, choosing how long to keep it, and clearing it safely when things change.

Laravel Caching in Practice: What to Cache, TTLs and Invalidation

Caching is one of the quickest ways to make a Laravel application feel faster. It is also one of the easiest ways to show users old or wrong data. The difference comes down to a few simple decisions.

This article walks through those decisions in plain language: what to cache, how long to keep it, how to clear it, and how to know it actually helped.

In short
  • Fix slow queries first, then cache what is still expensive.
  • Choose a TTL by asking how old the data is allowed to be.
  • Clear related keys when data changes, and measure the result.

Why cache at all?

Every time a page rebuilds the same result from scratch, it spends database time, CPU and your users' patience. A cache stores the result of that work so the next request can simply reuse it.

Done well, caching lowers database load and makes response times steady. Done carelessly, it serves outdated data and hides problems that should have been fixed in the query itself.

Rule of thumb: fix slow queries first (indexes, eager loading, fewer round trips). Cache what is still expensive after that.

Deciding what to cache

Good candidates share three things: they are expensive to build, requested often, and fine if slightly old. Dashboards, reports, settings and reference lists usually qualify. Account balances and stock levels at checkout usually do not.

PHP · app/Http/Controllers/ReportController.php
public function index(Request $request)
{
    $tenantId = $request->user()->tenant_id;

    $report = Cache::remember(
        "reports:sales:{$tenantId}",
        now()->addMinutes(10),
        fn () => SalesReport::forTenant($tenantId)->build()
    );

    return view('reports.index', compact('report'));
}

Cache::remember() does the whole job in one call: it returns the cached value if it exists, otherwise it runs the closure, stores the result and returns it.

Notice the tenant ID inside the key. If your app serves several customers, a key that forgets who the data belongs to can show one customer another customer's data.

Naming your keys

A clear key name saves hours of debugging later. A simple pattern works well: area:thing:id.

  • settings:site for global settings
  • products:list:page-2 for a paginated list
  • reports:sales:42 for a report that belongs to tenant 42

Readable keys are easy to find in Redis, easy to clear in groups, and hard to mix up.

Choosing a TTL

The time-to-live (TTL) is really a business question: how old can this data be before someone is confused or harmed?

Data Changes Typical TTL Clear on write?
App settings Rarely Hours or forever Yes
Dashboard numbers Constantly 1–10 minutes Optional
Product catalogue Sometimes 30–60 minutes Yes
User permissions Sometimes Until changed Always

Clearing the cache when data changes

There are three common approaches, and most applications use a mix:

  1. Let it expire. The TTL does the work. Simple, but data can be stale until it expires.
  2. Clear on change. Remove related keys when the underlying model is saved or deleted.
  3. Versioned keys. Put a version number in the key, so old entries are simply never read again.
PHP · app/Observers/OrderObserver.php
class OrderObserver
{
    public function saved(Order $order): void
    {
        // Tags need a store that supports them, such as Redis.
        Cache::tags(["tenant:{$order->tenant_id}", 'reports'])->flush();
    }
}

Avoiding a cache stampede

When a popular key expires, many requests may try to rebuild it at the same moment and overload the database. An atomic lock lets only one request rebuild the value while the others wait for a moment.

PHP · Rebuilding a value with an atomic lock
$report = Cache::get($key);

if ($report === null) {
    $report = Cache::lock("lock:{$key}", 10)->block(5, function () use ($key) {
        // Another request may have rebuilt it while we waited.
        return Cache::remember($key, 600, fn () => buildReport());
    });
}
Atomic locks need a cache driver that supports them, such as Redis, Memcached, database or file.

Measuring the result

  • Record response times before and after, at the same traffic level.
  • Track your hit ratio: a cache that is rarely hit adds complexity without benefit.
  • Watch database load, not just page speed.

Summary

Caching is a way to reuse expensive work safely. Pick candidates carefully, set TTLs based on how old the data may be, clear related keys when data changes, and check the improvement with real numbers.

Laravel Caching Masterclass: Cache, Redis & Performance course thumbnail
Go deeper

Laravel Caching Masterclass: Cache, Redis & Performance

Step-by-step lessons on everything in this article, plus Redis, full-page caching and cache analytics, applied to a real application.

View Course
Read next

Other articles