DEV Community

Cover image for Stop Database Bottlenecks: Master Redis Cache Tags in Laravel
Prajapati Paresh
Prajapati Paresh

Posted on • Originally published at smarttechdevs.in

Stop Database Bottlenecks: Master Redis Cache Tags in Laravel

The Database Bottleneck in B2B SaaS

When building enterprise applications at Smart Tech Devs, we know that database queries are expensive. As your SaaS platform scales and thousands of users access complex dashboards simultaneously, relying solely on your PostgreSQL or MySQL database to calculate aggregate data on the fly will eventually bottleneck your servers. Even with perfect indexing, highly concurrent reads on heavy tables (like financial reports or global analytics) will spike CPU usage and degrade API response times.

The solution is an aggressive caching strategy. However, caching is famously one of the hardest problems in computer science—not because storing data is hard, but because knowing exactly when to delete it (Cache Invalidation) is incredibly complex.

Beyond Simple Caching: The Power of Cache Tags

Standard caching using Cache::remember() works great for static data. But what happens when you cache a list of invoices for a specific tenant, and that tenant creates a new invoice? If you used a generic cache key, you have to wait for the Time-To-Live (TTL) to expire before the user sees their new data. This is an unacceptable user experience in a modern B2B application.

By leveraging Redis as our cache driver in Laravel, we unlock the power of Cache Tags. Cache Tags allow us to label our cached data with multiple identifiers and then surgically flush only the affected data when a state change occurs.

Step 1: Implementing Tagged Caching in Repositories

Let's look at how we fetch and cache a tenant's dashboard analytics. We will tag the cache with both a generic tenant_analytics tag and a specific tenant_id tag.


namespace App\Repositories;

use App\Models\Tenant;
use Illuminate\Support\Facades\Cache;

class AnalyticsRepository
{
    /**
     * Get complex dashboard stats, heavily cached.
     */
    public function getDashboardStats(Tenant $tenant)
    {
        // Define a unique key for this specific query
        $cacheKey = "dashboard_stats_{$tenant->id}";

        // Use Cache::tags() to categorize this cached item
        return Cache::tags(['analytics', "tenant_{$tenant->id}"])->remember($cacheKey, now()->addHours(24), function () use ($tenant) {
            // Simulate an expensive, multi-table query aggregation
            return [
                'total_revenue' => $tenant->invoices()->sum('amount'),
                'active_users' => $tenant->users()->where('status', 'active')->count(),
                'recent_activity' => $tenant->activityLogs()->latest()->take(10)->get(),
            ];
        });
    }
}

Step 2: Elegant Cache Invalidation with Eloquent Observers

Now, we must ensure that when a tenant adds a new invoice or a new user joins, their dashboard updates instantly. Instead of littering our controllers with Cache::forget() calls, we use Laravel Eloquent Observers to listen for model events and invalidate the tags automatically.


namespace App\Observers;

use App\Models\Invoice;
use Illuminate\Support\Facades\Cache;

class InvoiceObserver
{
    /**
     * Handle the Invoice "created" event.
     */
    public function created(Invoice $invoice): void
    {
        // Surgically flush ONLY the analytics cache for the specific tenant
        // who just created an invoice. All other tenants' caches remain intact!
        Cache::tags(["tenant_{$invoice->tenant_id}"])->flush();
    }

    /**
     * Handle the Invoice "updated" event.
     */
    public function updated(Invoice $invoice): void
    {
        Cache::tags(["tenant_{$invoice->tenant_id}"])->flush();
    }
    
    // ... handle deleted, restored, etc.
}

The Architectural Wins

Transitioning to a tag-based Redis caching architecture provides immediate, scalable benefits:

  1. Zero Stale Data: Users get the speed of cached memory with the accuracy of live database queries because invalidation happens the exact millisecond underlying data changes.
  2. Surgical Precision: Flushing a tag only destroys the cache for the affected tenant. A traditional cache flush would clear the entire system, causing a massive traffic spike to your database as every user repopulates their cache simultaneously (Cache Stampede).
  3. Cleaner Controllers: By pushing invalidation logic to Observers, your business logic remains decoupled and clean.

Conclusion

Protecting your database is the key to scaling B2B SaaS. By mastering Redis Cache Tags and Eloquent Observers in Laravel, you can build dashboards that load in milliseconds while ensuring complete data integrity and system durability under massive load.

Top comments (0)