Caching Strategies for Performance: Mastering Laravel and Redis
Learn how to use caching in Laravel to slash database load. Master cache tags, store configuration, and invalidation strategies for high-performance apps.
Previously in this course, we covered Eloquent Performance Optimization to eliminate N+1 query issues. While eager loading is a mandatory first step, some operations—like calculating complex project statistics or fetching global settings—remain expensive no matter how well you optimize your SQL.
Today, we move beyond the database layer by implementing caching. We’ll focus on using Redis to store computed results, using cache tags for granular control, and ensuring your cache stays fresh through proper invalidation.
Why Caching Matters
Every time a user requests your project board, the server performs a round-trip to the database, deserializes rows into Eloquent models, and transforms them into JSON. On a high-traffic app, this is a bottleneck.
Caching is the practice of storing the result of an expensive operation in a fast, volatile storage medium (like RAM) so subsequent requests can skip the heavy lifting. In Laravel, we use the Cache facade to abstract this process, allowing us to swap drivers like file, database, or redis without changing our application logic.
Using Cache Stores and Redis
Laravel allows you to define multiple stores in config/cache.php. For production, Redis is the industry standard because it’s an in-memory data structure store that supports atomic operations and complex data types.
To use Redis, ensure you have the predis/predis or phpredis extension installed and update your .env:
BashCACHE_STORE=redis REDIS_CLIENT=phpredis
Now, you can interact with the store directly:
PHP#6A9955">// Storing data for 60 minutes Cache::store('redis')->put('project_stats_1', $data, now()->addMinutes(60)); #6A9955">// Retrieving with a fallback closure $stats = Cache::store('redis')->remember('project_stats_1', 3600, function () { return Project::calculateComplexStats(); });
Implementing Cache Tags
A common mistake is caching everything under a single key. When the underlying data changes, you're forced to clear the entire cache. Cache tags solve this by allowing you to group related cache items and purge them collectively.
Note: Cache tags are only supported by drivers like redis or memcached.
Imagine our project board has a list of tasks. We want to cache the tasks for a specific project, but we also want to cache them under a "tasks" tag so we can clear all task-related caches if a global update occurs.
PHP#6A9955">// Tagging cache items Cache::tags(['projects', 'tasks'])->put('project_1_tasks', $tasks, 3600); #6A9955">// Retrieving tagged items $tasks = Cache::tags(['projects', 'tasks'])->get('project_1_tasks'); #6A9955">// Invalidating everything under the 'projects' tag Cache::tags(['projects'])->flush();
Invalidation: The "Hard" Problem
Caching is easy; cache invalidation is where most engineers fail. If you cache a project’s task list but don't clear it when a task is added, the user sees stale data.
We solve this using observers or events. In our project board, every time a Task is created or updated, we must clear the relevant cache.
PHP#6A9955">// Inside a TaskObserver public function saved(Task $task) { #6A9955">// Clear the specific project cache tag Cache::tags(['project_' . $task->project_id])->flush(); }
This pattern ensures that the cache is always eventually consistent with the database. You can read more about managing complex dependencies in Database Caching Strategies: Mastering Partitioned Keys and Eviction to see how this scales.
Hands-on Exercise
In our ongoing project board, open your TaskService.
- Identify the method that fetches the list of tasks for a project.
- Wrap that query in a
Cache::tags(['project_' . $projectId])->remember(...)block. - Create a
TaskObserverand use thesavedanddeletedhooks to flush theproject_{id}tag. - Verify that the database query count drops when refreshing the page after the first load.
Common Pitfalls
- Caching dynamic data: Never cache data that changes every second (like a user's current session or CSRF tokens). Use cache for read-heavy, write-rare data.
- Ignoring Serialization: When using Redis, Laravel serializes your objects. Ensure your data is serializable (avoid caching open database connections or file handles).
- Over-caching: Caching everything leads to "cache stampedes," where your cache becomes more complex to maintain than your database queries. Cache only what is demonstrably slow.
- Forgetting Time-To-Live (TTL): Always set an expiration. If you don't, your cache will grow until your Redis memory limit is reached, causing it to evict keys unpredictably.
Recap
Performance requires a multi-layered approach. By moving expensive calculations into Redis using cache tags, we reduce the load on our primary database. Remember: cache invalidation is the most critical part of the strategy—if your data is stale, the performance gain is irrelevant.
Up next: We’ll explore how to clean up our code further by Using Traits for Code Reuse, allowing us to share common caching and logic patterns across our models and services.
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.
