Eloquent Caching Strategies: Scaling Laravel Performance
Master Eloquent caching to minimize database hits. Learn how to wrap model lookups in Redis and automate cache invalidation using Laravel's model events.
Previously in this course, we explored Advanced Indexing Strategies to optimize how the database engine retrieves data. While indexes reduce the cost of a query, the most performant query is the one you never send to the database at all.
In this lesson, we move from optimizing database reads to eliminating them entirely through model-level caching. We will build a robust pattern for caching individual Eloquent instances and ensuring data consistency through automated invalidation.
Why Cache at the Model Level?
In a high-traffic SaaS, repetitive lookups for static or semi-static data—like "Plan Settings," "User Profiles," or "Organization Metadata"—account for a massive percentage of your database CPU cycles.
While we’ve previously discussed Caching Strategies for Performance: Mastering Laravel and Redis, applying those concepts to Eloquent requires a disciplined approach to cache keys and lifecycle management. Without a strategy, you end up with "stale data bugs," where a user updates their profile, but the application continues to serve the old cached version.
Implementing the Repository-Cache Pattern
To keep our architecture clean, we avoid cluttering our Eloquent models with cache logic. Instead, we use a service-based approach to wrap the retrieval process.
The Worked Example: Caching a User Profile
Let's assume our SaaS platform frequently fetches a User model by ID. Instead of hitting the DB, we'll check Redis first.
PHPnamespace App\Services; use App\Models\User; use Illuminate\Support\Facades\Cache; class UserService { public function findById(int $id): User { #6A9955">// Use a cache key specific to the model and ID return Cache::remember("user:{$id}", now()->addHours(24), function () use ($id) { return User::findOrFail($id); }); } }
This remember method is the workhorse of Eloquent caching. It checks the key; if it's missing, it executes the closure and stores the result.
Automating Invalidation with Model Events
The biggest pitfall in caching is the "stale data" trap. When a model is updated or deleted, the cached version is now a lie. Laravel's model events provide the perfect hook for invalidation.
We will use a Trait to keep our models clean and reusable.
PHPnamespace App\Traits; use Illuminate\Support\Facades\Cache; trait Cachable { public static function bootCachable() { static::updated(function ($model) { Cache::forget(static::getCacheKey($model->id)); }); static::deleted(function ($model) { Cache::forget(static::getCacheKey($model->id)); }); } public static function getCacheKey(int $id): string { return strtolower(class_basename(static::class)) . ":{$id}"; } }
Now, simply apply this trait to your User model. Any time you call $user->update(), the cache is automatically purged, forcing the UserService to fetch a fresh copy on the next request.
Hands-on Exercise
- Create a
Cachabletrait as shown above. - Apply it to your
Subscriptionmodel. - Refactor your
SubscriptionServiceto useCache::rememberwhen fetching subscriptions. - Verify the behavior: Use
php artisan tinkerto fetch a subscription, then update the database directly via SQL (bypassing the model), and observe that the app still serves the cached version. Then, update the model via Eloquent and observe the cache clear.
Common Pitfalls
- Caching Collections: Avoid caching large collections. If you cache a query that returns 500 rows, you lose the ability to perform efficient
wherefilters on that data. Cache individual models or small, specific datasets only. - Serialization Issues: Ensure your cached objects are serializable. If you store a model that contains an open database connection or a non-serializable closure in its attributes, you will encounter
Serialization of 'Closure' is not allowederrors. - The "Cache Stampede": If a highly popular cache key expires, multiple concurrent requests might try to re-populate the cache simultaneously. Use
Cache::rememberwhich handles the lock internally, or consider pre-warming your cache for critical items.
Summary
By implementing a standardized caching layer for our Eloquent models, we significantly reduce database overhead. The key is to treat the cache as a secondary, volatile store and rely on model events to ensure that the "source of truth" (the database) and the "speed layer" (Redis) stay synchronized.
Up next, we will dive into Queue Worker Prioritization, where we'll learn how to ensure that critical background tasks—like sending password reset emails—are never stuck behind low-priority batch processing.
Work with me

Laravel REST API Development
Clean, secure, well-documented Laravel REST APIs — the backend engine for your app, mobile client, or SaaS. Built by an API specialist.

FilamentPHP Admin Panel & Dashboard Development
A powerful admin panel for your Laravel app — built with FilamentPHP so you can manage everything without touching the database.