Laravel Caching: Boosting Performance with Redis and Memcached

Published on December 01, 2025
Laravel Caching Redis Memcached Performance Optimization

Introduction to Laravel Caching

Caching is one of the most effective ways to dramatically improve your Laravel application's performance. By storing frequently accessed data in memory, you can reduce database queries, API calls, and expensive computations, resulting in faster response times and better user experience.

Why Use Caching?

  • Reduce database load and queries
  • Improve application response times
  • Handle higher traffic volumes
  • Reduce external API calls
  • Save computational resources
  • Improve user experience

Common Use Cases:

  • Frequently accessed database queries
  • Expensive computations
  • API responses from external services
  • Compiled views and templates
  • Session and authentication data
  • Real-time data with frequent reads

Cache Configuration

Supported Cache Drivers

// config/cache.php

return [
    'default' => env('CACHE_DRIVER', 'file'),

    'stores' => [
        'apc' => [
            'driver' => 'apc',
        ],

        'array' => [
            'driver' => 'array',
            'serialize' => false,
        ],

        'database' => [
            'driver' => 'database',
            'table' => 'cache',
            'connection' => null,
            'lock_connection' => null,
        ],

        'file' => [
            'driver' => 'file',
            'path' => storage_path('framework/cache/data'),
            'lock_path' => storage_path('framework/cache/data'),
        ],

        'memcached' => [
            'driver' => 'memcached',
            'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
            'sasl' => [
                env('MEMCACHED_USERNAME'),
                env('MEMCACHED_PASSWORD'),
            ],
            'options' => [
                // Memcached::OPT_CONNECT_TIMEOUT => 2000,
            ],
            'servers' => [
                [
                    'host' => env('MEMCACHED_HOST', '127.0.0.1'),
                    'port' => env('MEMCACHED_PORT', 11211),
                    'weight' => 100,
                ],
            ],
        ],

        'redis' => [
            'driver' => 'redis',
            'connection' => 'cache',
            'lock_connection' => 'default',
        ],

        'dynamodb' => [
            'driver' => 'dynamodb',
            'key' => env('AWS_ACCESS_KEY_ID'),
            'secret' => env('AWS_SECRET_ACCESS_KEY'),
            'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
            'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
            'endpoint' => env('DYNAMODB_ENDPOINT'),
        ],

        'octane' => [
            'driver' => 'octane',
        ],
    ],

    'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
];

Environment Configuration

# .env file
CACHE_DRIVER=redis

# Redis Configuration
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
REDIS_DB=0

# Memcached Configuration
MEMCACHED_HOST=127.0.0.1
MEMCACHED_PORT=11211
MEMCACHED_USERNAME=null
MEMCACHED_PASSWORD=null

# Database Cache (if using database driver)
DB_CONNECTION=mysql

Setting Up Redis

# Install Redis on Ubuntu
sudo apt update
sudo apt install redis-server

# Start Redis service
sudo systemctl start redis-server
sudo systemctl enable redis-server

# Test Redis connection
redis-cli ping

# Install Redis PHP extension
sudo apt install php-redis
sudo systemctl restart php-fpm  # or your PHP service

# Clear configuration cache
php artisan config:clear

Setting Up Memcached

# Install Memcached on Ubuntu
sudo apt update
sudo apt install memcached

# Install PHP Memcached extension
sudo apt install php-memcached

# Start Memcached service
sudo systemctl start memcached
sudo systemctl enable memcached

# Check Memcached status
systemctl status memcached

# Test Memcached connection
telnet localhost 11211

Basic Cache Operations

Storing and Retrieving Items

<?php
// app/Services/CacheService.php

namespace App\Services;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

class CacheService
{
    public function basicCacheOperations()
    {
        // Store a value in cache for 60 minutes
        Cache::put('key', 'value', 60 * 60);
        
        // Store a value forever
        Cache::forever('key', 'value');
        
        // Store if not exists
        Cache::add('key', 'value', 60);
        
        // Retrieve a value
        $value = Cache::get('key');
        
        // Retrieve with default value
        $value = Cache::get('key', 'default');
        
        // Retrieve or store if not exists
        $value = Cache::remember('key', 60, function () {
            return 'computed value';
        });
        
        // Retrieve and delete
        $value = Cache::pull('key');
        
        // Check if key exists
        if (Cache::has('key')) {
            $value = Cache::get('key');
        }
        
        // Delete a key
        Cache::forget('key');
        
        // Clear entire cache
        Cache::flush();
    }
    
    public function cacheMultipleItems()
    {
        // Store multiple items
        Cache::putMany([
            'user_1' => 'John Doe',
            'user_2' => 'Jane Smith',
            'settings' => ['theme' => 'dark', 'language' => 'en'],
        ], 60);
        
        // Get multiple items
        $values = Cache::many(['user_1', 'user_2', 'settings']);
        
        // Store multiple items forever
        Cache::putManyForever([
            'config_site_name' => 'My App',
            'config_version' => '1.0.0',
        ]);
    }
    
    public function atomicCacheOperations()
    {
        // Increment a value
        Cache::increment('page_views');
        Cache::increment('page_views', 5);
        
        // Decrement a value
        Cache::decrement('stock_count');
        Cache::decrement('stock_count', 3);
        
        // Get and increment (atomic)
        $current = Cache::get('counter', 0);
        $new = Cache::increment('counter');
    }
}

Cache Helper Functions

<?php
// Using global cache() helper

// Store value
cache(['key' => 'value'], 300); // 5 minutes

// Retrieve value
$value = cache('key');

// Retrieve with default
$value = cache('key', function () {
    return 'default value';
});

// Store if not exists
$value = cache()->remember('key', 60, function () {
    return expensiveComputation();
});

// Clear specific key
cache()->forget('key');

// Clear all cache
cache()->flush();

Real-World Caching Examples

Database Query Caching

<?php
// app/Services/ProductService.php

namespace App\Services;

use App\Models\Product;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;

class ProductService
{
    public function getPopularProducts($limit = 10)
    {
        $cacheKey = "products:popular:{$limit}";
        $cacheDuration = 3600; // 1 hour
        
        return Cache::remember($cacheKey, $cacheDuration, function () use ($limit) {
            return Product::with(['category', 'brand'])
                ->where('status', 'active')
                ->where('stock_quantity', '>', 0)
                ->withCount(['orders', 'reviews'])
                ->orderByDesc('orders_count')
                ->limit($limit)
                ->get()
                ->map(function ($product) {
                    return [
                        'id' => $product->id,
                        'name' => $product->name,
                        'slug' => $product->slug,
                        'price' => $product->price / 100,
                        'image' => $product->primary_image_url,
                        'rating' => $product->average_rating,
                        'reviews_count' => $product->reviews_count,
                        'category' => $product->category->name,
                    ];
                });
        });
    }
    
    public function getProductWithCache($productId)
    {
        $cacheKey = "product:{$productId}:full";
        $cacheDuration = 1800; // 30 minutes
        
        return Cache::remember($cacheKey, $cacheDuration, function () use ($productId) {
            return Product::with([
                'category',
                'brand',
                'variants',
                'reviews' => function ($query) {
                    $query->with('user:id,name,avatar_url')
                          ->where('approved', true)
                          ->latest()
                          ->limit(10);
                },
                'relatedProducts' => function ($query) {
                    $query->select('id', 'name', 'slug', 'price', 'primary_image_url')
                          ->where('status', 'active');
                },
            ])->findOrFail($productId);
        });
    }
    
    public function getProductStats()
    {
        $cacheKey = 'stats:products:global';
        $cacheDuration = 300; // 5 minutes
        
        return Cache::remember($cacheKey, $cacheDuration, function () {
            return [
                'total_products' => Product::count(),
                'active_products' => Product::where('status', 'active')->count(),
                'out_of_stock' => Product::where('stock_quantity', 0)->count(),
                'total_value' => Product::sum('price') / 100,
                'average_price' => Product::average('price') / 100,
                'by_category' => DB::table('products')
                    ->join('categories', 'products.category_id', '=', 'categories.id')
                    ->select('categories.name', DB::raw('COUNT(*) as count'))
                    ->groupBy('categories.id', 'categories.name')
                    ->get()
                    ->pluck('count', 'name'),
            ];
        });
    }
    
    public function clearProductCache($productId = null)
    {
        if ($productId) {
            // Clear specific product cache
            Cache::forget("product:{$productId}:full");
            Cache::forget("product:{$productId}:basic");
            
            // Clear related caches
            Cache::forget('products:popular:10');
            Cache::forget('products:popular:20');
            Cache::forget('stats:products:global');
        } else {
            // Clear all product-related caches with tags
            Cache::tags(['products'])->flush();
        }
    }
}

API Response Caching

<?php
// app/Services/ApiCacheService.php

namespace App\Services;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;

class ApiCacheService
{
    public function getWeatherData($city, $country = null)
    {
        $cacheKey = "weather:{$city}:{$country}";
        $cacheDuration = 1800; // 30 minutes
        
        return Cache::remember($cacheKey, $cacheDuration, function () use ($city, $country) {
            $apiKey = config('services.weather.api_key');
            $location = $country ? "{$city},{$country}" : $city;
            
            $response = Http::timeout(10)
                ->retry(3, 100)
                ->get("https://api.openweathermap.org/data/2.5/weather", [
                    'q' => $location,
                    'appid' => $apiKey,
                    'units' => 'metric',
                ]);
            
            if ($response->failed()) {
                throw new \Exception("Weather API failed: " . $response->body());
            }
            
            $data = $response->json();
            
            return [
                'temperature' => $data['main']['temp'],
                'feels_like' => $data['main']['feels_like'],
                'humidity' => $data['main']['humidity'],
                'pressure' => $data['main']['pressure'],
                'description' => $data['weather'][0]['description'],
                'icon' => $data['weather'][0]['icon'],
                'wind_speed' => $data['wind']['speed'],
                'wind_direction' => $data['wind']['deg'],
                'visibility' => $data['visibility'],
                'sunrise' => $data['sys']['sunrise'],
                'sunset' => $data['sys']['sunset'],
                'last_updated' => now()->toISOString(),
            ];
        });
    }
    
    public function getExchangeRates($baseCurrency = 'USD')
    {
        $cacheKey = "exchange_rates:{$baseCurrency}";
        $cacheDuration = 3600; // 1 hour
        
        return Cache::remember($cacheKey, $cacheDuration, function () use ($baseCurrency) {
            $response = Http::timeout(10)
                ->retry(3, 100)
                ->get("https://api.exchangerate-api.com/v4/latest/{$baseCurrency}");
            
            if ($response->failed()) {
                // Fallback to stored rates
                return $this->getFallbackRates($baseCurrency);
            }
            
            $data = $response->json();
            
            return [
                'base' => $data['base'],
                'date' => $data['date'],
                'rates' => $data['rates'],
                'timestamp' => now()->timestamp,
                'source' => 'api',
            ];
        });
    }
    
    public function cacheApiResponse(Request $request, $cacheDuration = 300)
    {
        $cacheKey = $this->generateCacheKey($request);
        
        return Cache::remember($cacheKey, $cacheDuration, function () use ($request) {
            // Simulate API call
            return [
                'data' => 'API Response Data',
                'timestamp' => now()->toISOString(),
                'request_id' => Str::uuid(),
            ];
        });
    }
    
    protected function generateCacheKey(Request $request): string
    {
        $path = $request->path();
        $query = $request->query();
        $userId = $request->user()?->id;
        
        ksort($query); // Sort query parameters for consistent keys
        
        return sprintf(
            'api:%s:%s:%s',
            $path,
            md5(serialize($query)),
            $userId ?: 'guest'
        );
    }
    
    protected function getFallbackRates($baseCurrency): array
    {
        // Return cached rates or default rates
        $defaultRates = [
            'USD' => 1.0,
            'EUR' => 0.85,
            'GBP' => 0.73,
            'JPY' => 110.0,
            'CAD' => 1.25,
        ];
        
        $baseRate = $defaultRates[$baseCurrency] ?? 1.0;
        
        $rates = [];
        foreach ($defaultRates as $currency => $rate) {
            $rates[$currency] = $rate / $baseRate;
        }
        
        return [
            'base' => $baseCurrency,
            'date' => now()->format('Y-m-d'),
            'rates' => $rates,
            'timestamp' => now()->timestamp,
            'source' => 'fallback',
        ];
    }
}

View Caching

<?php
// app/Services/ViewCacheService.php

namespace App\Services;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\View;

class ViewCacheService
{
    public function cacheComplexView($viewName, $data, $cacheDuration = 3600)
    {
        $cacheKey = "view:{$viewName}:" . md5(serialize($data));
        
        return Cache::remember($cacheKey, $cacheDuration, function () use ($viewName, $data) {
            return View::make($viewName, $data)->render();
        });
    }
    
    public function getCachedMenu($menuName)
    {
        $cacheKey = "menu:{$menuName}";
        $cacheDuration = 86400; // 24 hours
        
        return Cache::remember($cacheKey, $cacheDuration, function () use ($menuName) {
            return $this->buildMenu($menuName);
        });
    }
    
    protected function buildMenu($menuName): array
    {
        // Complex menu building logic
        $menu = [
            'home' => [
                'title' => 'Home',
                'url' => '/',
                'icon' => 'home',
                'children' => [],
            ],
            'products' => [
                'title' => 'Products',
                'url' => '/products',
                'icon' => 'shopping-bag',
                'children' => $this->getProductCategories(),
            ],
            // ... more menu items
        ];
        
        return $menu;
    }
    
    protected function getProductCategories(): array
    {
        // This would typically come from database
        return Cache::remember('menu:product_categories', 3600, function () {
            return \App\Models\Category::withCount('products')
                ->where('active', true)
                ->orderBy('order')
                ->get()
                ->map(function ($category) {
                    return [
                        'title' => $category->name,
                        'url' => "/products/category/{$category->slug}",
                        'count' => $category->products_count,
                    ];
                })->toArray();
        });
    }
}

Advanced Caching Strategies

Cache Tags

<?php
// app/Services/TaggedCacheService.php

namespace App\Services;

use Illuminate\Support\Facades\Cache;
use App\Models\User;
use App\Models\Product;
use App\Models\Order;

class TaggedCacheService
{
    public function getUserWithTags($userId)
    {
        $cacheKey = "user:{$userId}:profile";
        
        return Cache::tags(['users', "user:{$userId}"])
            ->remember($cacheKey, 3600, function () use ($userId) {
                return User::with(['profile', 'orders', 'addresses'])
                    ->findOrFail($userId);
            });
    }
    
    public function getProductWithTags($productId)
    {
        $cacheKey = "product:{$productId}:full";
        
        return Cache::tags(['products', "product:{$productId}"])
            ->remember($cacheKey, 1800, function () use ($productId) {
                return Product::with(['category', 'brand', 'variants', 'reviews'])
                    ->findOrFail($productId);
            });
    }
    
    public function clearUserCache($userId)
    {
        // Clear all cache tagged with this user
        Cache::tags(["user:{$userId}"])->flush();
        
        // Also clear general user cache
        Cache::tags(['users'])->forget("user:{$userId}:profile");
        Cache::tags(['users'])->forget("user:{$userId}:orders");
        Cache::tags(['users'])->forget("user:{$userId}:stats");
    }
    
    public function clearProductCache($productId)
    {
        Cache::tags(["product:{$productId}"])->flush();
        Cache::tags(['products'])->forget("product:{$productId}:full");
        Cache::tags(['products'])->forget("product:{$productId}:basic");
        
        // Clear related caches
        Cache::tags(['products'])->forget('products:popular');
        Cache::tags(['products'])->forget('products:featured');
    }
    
    public function clearAllCache()
    {
        // Clear cache by tags
        Cache::tags(['users'])->flush();
        Cache::tags(['products'])->flush();
        Cache::tags(['orders'])->flush();
        Cache::tags(['categories'])->flush();
        
        // Clear all tagged cache
        Cache::flush();
    }
    
    public function getCachedDataWithMultipleTags()
    {
        $cacheKey = 'dashboard:stats';
        
        return Cache::tags(['dashboard', 'stats', 'global'])
            ->remember($cacheKey, 300, function () {
                return [
                    'users' => [
                        'total' => User::count(),
                        'active' => User::where('active', true)->count(),
                        'new_today' => User::whereDate('created_at', today())->count(),
                    ],
                    'products' => [
                        'total' => Product::count(),
                        'out_of_stock' => Product::where('stock_quantity', 0)->count(),
                        'low_stock' => Product::where('stock_quantity', '<', 10)->count(),
                    ],
                    'orders' => [
                        'total' => Order::count(),
                        'pending' => Order::where('status', 'pending')->count(),
                        'today' => Order::whereDate('created_at', today())->count(),
                        'revenue_today' => Order::whereDate('created_at', today())->sum('total_amount'),
                    ],
                    'updated_at' => now()->toISOString(),
                ];
            });
    }
}

Cache Locking

<?php
// app/Services/CacheLockService.php

namespace App\Services;

use Illuminate\Support\Facades\Cache;
use Illuminate\Cache\Lock;
use Illuminate\Support\Facades\Log;

class CacheLockService
{
    public function processWithLock($key, $callback, $lockTimeout = 10)
    {
        $lock = Cache::lock($key, $lockTimeout);
        
        try {
            if ($lock->get()) {
                // We have the lock, process the callback
                Log::info("Acquired lock for key: {$key}");
                
                try {
                    $result = $callback();
                } finally {
                    $lock->release();
                    Log::info("Released lock for key: {$key}");
                }
                
                return $result;
            } else {
                // Could not acquire lock, handle accordingly
                Log::warning("Could not acquire lock for key: {$key}");
                throw new \Exception("Could not acquire lock for {$key}");
            }
        } catch (\Throwable $e) {
            // Ensure lock is released on error
            if (isset($lock) && $lock->get()) {
                $lock->release();
            }
            throw $e;
        }
    }
    
    public function updateProductStock($productId, $quantityChange)
    {
        $lockKey = "product:{$productId}:stock:lock";
        
        return $this->processWithLock($lockKey, function () use ($productId, $quantityChange) {
            // This code will only run by one process at a time
            $product = \App\Models\Product::findOrFail($productId);
            
            // Simulate complex stock calculation
            sleep(1);
            
            $newStock = $product->stock_quantity + $quantityChange;
            
            if ($newStock < 0) {
                throw new \Exception("Insufficient stock");
            }
            
            $product->update(['stock_quantity' => $newStock]);
            
            // Clear product cache
            Cache::forget("product:{$productId}:stock");
            Cache::forget("product:{$productId}:full");
            
            return [
                'product_id' => $productId,
                'old_stock' => $product->stock_quantity,
                'new_stock' => $newStock,
                'change' => $quantityChange,
            ];
        }, 30); // 30 second lock timeout
    }
    
    public function processQueueWithLock($queueName, $callback, $timeout = 60)
    {
        $lockKey = "queue:{$queueName}:lock";
        
        return Cache::lock($lockKey, $timeout)->block(10, function () use ($callback) {
            // Block for up to 10 seconds waiting for the lock
            return $callback();
        });
    }
    
    public function getOrSetWithLock($key, $callback, $ttl = 3600, $lockTimeout = 10)
    {
        // Try to get cached value first
        if (Cache::has($key)) {
            return Cache::get($key);
        }
        
        // Use lock to prevent cache stampede
        $lock = Cache::lock("{$key}:lock", $lockTimeout);
        
        try {
            if ($lock->get()) {
                // We have the lock, compute and cache the value
                $value = $callback();
                Cache::put($key, $value, $ttl);
                $lock->release();
                return $value;
            } else {
                // Wait for other process to compute the value
                $lock->block(5); // Wait up to 5 seconds
                return Cache::get($key);
            }
        } catch (\Throwable $e) {
            if (isset($lock) && $lock->get()) {
                $lock->release();
            }
            throw $e;
        }
    }
}

Cache Warming and Preloading

<?php
// app/Console/Commands/WarmCache.php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
use App\Models\Product;
use App\Models\User;
use App\Models\Category;

class WarmCache extends Command
{
    protected $signature = 'cache:warm 
                           {--all : Warm all caches}
                           {--products : Warm product caches}
                           {--users : Warm user caches}
                           {--categories : Warm category caches}';
    
    protected $description = 'Warm frequently used caches to improve performance';
    
    public function handle()
    {
        $this->info('Starting cache warming...');
        
        if ($this->option('all') || $this->option('products')) {
            $this->warmProductCaches();
        }
        
        if ($this->option('all') || $this->option('users')) {
            $this->warmUserCaches();
        }
        
        if ($this->option('all') || $this->option('categories')) {
            $this->warmCategoryCaches();
        }
        
        $this->info('Cache warming completed!');
    }
    
    protected function warmProductCaches()
    {
        $this->info('Warming product caches...');
        
        // Warm popular products cache
        $popularProducts = Product::with(['category', 'brand'])
            ->where('status', 'active')
            ->where('stock_quantity', '>', 0)
            ->withCount(['orders', 'reviews'])
            ->orderByDesc('orders_count')
            ->limit(20)
            ->get();
        
        Cache::put('products:popular:20', $popularProducts, 3600);
        $this->line('✓ Popular products cached');
        
        // Warm featured products
        $featuredProducts = Product::with(['category'])
            ->where('featured', true)
            ->where('status', 'active')
            ->limit(10)
            ->get();
        
        Cache::put('products:featured', $featuredProducts, 3600);
        $this->line('✓ Featured products cached');
        
        // Warm new arrivals
        $newProducts = Product::with(['category'])
            ->where('status', 'active')
            ->orderByDesc('created_at')
            ->limit(15)
            ->get();
        
        Cache::put('products:new', $newProducts, 1800);
        $this->line('✓ New products cached');
        
        // Warm product counts by category
        $categories = Category::withCount(['products' => function ($query) {
            $query->where('status', 'active');
        }])->get();
        
        foreach ($categories as $category) {
            Cache::put("category:{$category->id}:product_count", $category->products_count, 86400);
        }
        $this->line('✓ Product counts by category cached');
    }
    
    protected function warmUserCaches()
    {
        $this->info('Warming user caches...');
        
        // Warm user stats
        $userStats = [
            'total' => User::count(),
            'active' => User::where('active', true)->count(),
            'new_today' => User::whereDate('created_at', today())->count(),
            'verified' => User::whereNotNull('email_verified_at')->count(),
        ];
        
        Cache::put('stats:users', $userStats, 1800);
        $this->line('✓ User stats cached');
        
        // Warm top users
        $topUsers = User::withCount(['orders', 'reviews'])
            ->orderByDesc('orders_count')
            ->limit(10)
            ->get()
            ->map(function ($user) {
                return [
                    'id' => $user->id,
                    'name' => $user->name,
                    'email' => $user->email,
                    'orders_count' => $user->orders_count,
                    'total_spent' => $user->orders()->sum('total_amount'),
                ];
            });
        
        Cache::put('users:top', $topUsers, 3600);
        $this->line('✓ Top users cached');
    }
    
    protected function warmCategoryCaches()
    {
        $this->info('Warming category caches...');
        
        // Warm category tree
        $categories = Category::with(['children' => function ($query) {
            $query->withCount('products');
        }])
        ->whereNull('parent_id')
        ->orderBy('order')
        ->get();
        
        Cache::put('categories:tree', $categories, 86400);
        $this->line('✓ Category tree cached');
        
        // Warm all categories with product counts
        $allCategories = Category::withCount(['products' => function ($query) {
            $query->where('status', 'active');
        }])->get();
        
        Cache::put('categories:all', $allCategories, 86400);
        $this->line('✓ All categories cached');
    }
}

Redis-Specific Features

Redis Data Structures

<?php
// app/Services/RedisService.php

namespace App\Services;

use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Log;

class RedisService
{
    public function useRedisDataStructures()
    {
        // Strings
        Redis::set('user:1:name', 'John Doe');
        Redis::setex('user:1:session', 3600, 'session_token'); // With expiry
        Redis::setnx('user:1:lock', 'processing'); // Set if not exists
        
        $name = Redis::get('user:1:name');
        
        // Hashes (perfect for objects)
        Redis::hmset('user:1', [
            'name' => 'John Doe',
            'email' => 'john@example.com',
            'age' => 30,
            'city' => 'New York',
        ]);
        
        $user = Redis::hgetall('user:1');
        $email = Redis::hget('user:1', 'email');
        
        // Increment hash field
        Redis::hincrby('user:1', 'login_count', 1);
        
        // Lists (queues, stacks)
        Redis::rpush('queue:emails', json_encode(['to' => 'user@example.com']));
        Redis::lpush('recent:users', 'user:1');
        Redis::ltrim('recent:users', 0, 9); // Keep only 10 items
        
        $nextJob = Redis::lpop('queue:emails');
        $recentUsers = Redis::lrange('recent:users', 0, -1);
        
        // Sets (unique items)
        Redis::sadd('online:users', 'user:1');
        Redis::sadd('online:users', 'user:2');
        
        $onlineUsers = Redis::smembers('online:users');
        $isOnline = Redis::sismember('online:users', 'user:1');
        
        // Sorted Sets (leaderboards, rankings)
        Redis::zadd('leaderboard', 100, 'player:1');
        Redis::zadd('leaderboard', 85, 'player:2');
        Redis::zadd('leaderboard', 120, 'player:3');
        
        // Increment score
        Redis::zincrby('leaderboard', 10, 'player:1');
        
        $topPlayers = Redis::zrevrange('leaderboard', 0, 9, 'WITHSCORES');
        $playerRank = Redis::zrevrank('leaderboard', 'player:1');
        
        return [
            'user' => $user,
            'online_users' => $onlineUsers,
            'top_players' => $topPlayers,
        ];
    }
    
    public function implementLeaderboard()
    {
        $leaderboardKey = 'game:leaderboard';
        
        // Add or update player scores
        Redis::zadd($leaderboardKey, [
            'player_123' => 1500,
            'player_456' => 1420,
            'player_789' => 1380,
            'player_101' => 1550,
        ]);
        
        // Get top 10 players
        $topPlayers = Redis::zrevrange($leaderboardKey, 0, 9, 'WITHSCORES');
        
        // Get player rank and score
        $playerRank = Redis::zrevrank($leaderboardKey, 'player_123');
        $playerScore = Redis::zscore($leaderboardKey, 'player_123');
        
        // Increment player score
        Redis::zincrby($leaderboardKey, 50, 'player_123');
        
        // Get players in score range
        $playersInRange = Redis::zrangebyscore($leaderboardKey, 1400, 1600, [
            'WITHSCORES' => true,
        ]);
        
        return [
            'top_players' => $topPlayers,
            'player_123' => [
                'rank' => $playerRank,
                'score' => $playerScore,
            ],
            'players_1400_1600' => $playersInRange,
        ];
    }
    
    public function implementRateLimiting($userId, $action, $limit = 10, $window = 60)
    {
        $key = "rate_limit:{$userId}:{$action}";
        
        // Get current count
        $current = Redis::get($key) ?: 0;
        
        if ($current >= $limit) {
            return [
                'allowed' => false,
                'remaining' => 0,
                'reset_in' => Redis::ttl($key),
            ];
        }
        
        // Increment count
        Redis::multi();
        Redis::incr($key);
        Redis::expire($key, $window);
        Redis::exec();
        
        $newCount = $current + 1;
        
        return [
            'allowed' => true,
            'remaining' => $limit - $newCount,
            'reset_in' => $window,
        ];
    }
    
    public function trackRealTimeAnalytics()
    {
        $today = now()->format('Y-m-d');
        
        // Page views
        Redis::incr("analytics:page_views:{$today}");
        Redis::incr("analytics:page_views:total");
        
        // Unique visitors (using HyperLogLog for approximate counting)
        $visitorId = request()->ip() . ':' . request()->userAgent();
        Redis::pfadd("analytics:unique_visitors:{$today}", $visitorId);
        Redis::pfadd("analytics:unique_visitors:total", $visitorId);
        
        // Real-time online users
        $userId = auth()->id();
        if ($userId) {
            Redis::sadd("analytics:online_users", $userId);
            Redis::expire("analytics:online_users", 300); // 5 minutes
        }
        
        // Get analytics data
        $pageViewsToday = Redis::get("analytics:page_views:{$today}") ?: 0;
        $uniqueVisitorsToday = Redis::pfcount("analytics:unique_visitors:{$today}");
        $onlineUsers = Redis::smembers("analytics:online_users");
        
        return [
            'page_views_today' => $pageViewsToday,
            'unique_visitors_today' => $uniqueVisitorsToday,
            'online_users_count' => count($onlineUsers),
            'online_users' => $onlineUsers,
        ];
    }
}

Performance Monitoring and Optimization

Cache Monitoring Command

<?php
// app/Console/Commands/MonitorCache.php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\DB;

class MonitorCache extends Command
{
    protected $signature = 'cache:monitor
                           {--driver= : Specific cache driver to monitor}
                           {--detailed : Show detailed cache information}';
    
    protected $description = 'Monitor cache performance and statistics';
    
    public function handle()
    {
        $driver = $this->option('driver') ?: config('cache.default');
        
        $this->info("Cache Driver: {$driver}");
        $this->info("=========================");
        
        switch ($driver) {
            case 'redis':
                $this->monitorRedis();
                break;
            case 'memcached':
                $this->monitorMemcached();
                break;
            case 'database':
                $this->monitorDatabaseCache();
                break;
            case 'file':
                $this->monitorFileCache();
                break;
            default:
                $this->error("Unsupported driver: {$driver}");
        }
        
        if ($this->option('detailed')) {
            $this->showDetailedCacheInfo();
        }
    }
    
    protected function monitorRedis()
    {
        try {
            $info = Redis::command('INFO');
            
            $this->info("Redis Server Information:");
            $this->table(['Metric', 'Value'], [
                ['Version', $info['redis_version']],
                ['Uptime (days)', round($info['uptime_in_days'], 2)],
                ['Connected Clients', $info['connected_clients']],
                ['Memory Used', $this->formatBytes($info['used_memory'])],
                ['Memory Peak', $this->formatBytes($info['used_memory_peak'])],
                ['Keyspace Hits', $info['keyspace_hits']],
                ['Keyspace Misses', $info['keyspace_misses']],
                ['Hit Rate', round($info['keyspace_hits'] / max(1, $info['keyspace_hits'] + $info['keyspace_misses']) * 100, 2) . '%'],
            ]);
            
            // Get keys count by pattern
            $this->info("\nCache Key Patterns:");
            $patterns = ['*', 'products:*', 'users:*', 'session:*', 'queue:*'];
            
            $keyCounts = [];
            foreach ($patterns as $pattern) {
                $count = Redis::command('KEYS', [$pattern]);
                $keyCounts[] = [$pattern, count($count)];
            }
            
            $this->table(['Pattern', 'Key Count'], $keyCounts);
            
        } catch (\Exception $e) {
            $this->error("Failed to connect to Redis: " . $e->getMessage());
        }
    }
    
    protected function monitorMemcached()
    {
        try {
            $memcached = app('cache')->store('memcached')->getStore()->getMemcached();
            $stats = $memcached->getStats();
            
            if (empty($stats)) {
                $this->error("No Memcached servers found");
                return;
            }
            
            $this->info("Memcached Server Information:");
            
            foreach ($stats as $server => $serverStats) {
                $this->info("\nServer: {$server}");
                $this->table(['Metric', 'Value'], [
                    ['Uptime (days)', round($serverStats['uptime'] / 86400, 2)],
                    ['Current Connections', $serverStats['curr_connections']],
                    ['Total Connections', $serverStats['total_connections']],
                    ['Get Hits', $serverStats['get_hits']],
                    ['Get Misses', $serverStats['get_misses']],
                    ['Hit Rate', round($serverStats['get_hits'] / max(1, $serverStats['get_hits'] + $serverStats['get_misses']) * 100, 2) . '%'],
                    ['Current Items', $serverStats['curr_items']],
                    ['Total Items', $serverStats['total_items']],
                    ['Bytes Used', $this->formatBytes($serverStats['bytes'])],
                    ['Evictions', $serverStats['evictions']],
                ]);
            }
            
        } catch (\Exception $e) {
            $this->error("Failed to get Memcached stats: " . $e->getMessage());
        }
    }
    
    protected function monitorDatabaseCache()
    {
        try {
            $table = config('cache.stores.database.table', 'cache');
            
            $stats = DB::table($table)
                ->select([
                    DB::raw('COUNT(*) as total_entries'),
                    DB::raw('SUM(LENGTH(key)) as total_key_size'),
                    DB::raw('SUM(LENGTH(value)) as total_value_size'),
                    DB::raw('AVG(TIMESTAMPDIFF(SECOND, expiration, NOW())) as avg_seconds_to_expire'),
                    DB::raw('SUM(CASE WHEN expiration < NOW() THEN 1 ELSE 0 END) as expired_entries'),
                ])
                ->first();
            
            $this->info("Database Cache Information:");
            $this->table(['Metric', 'Value'], [
                ['Total Entries', $stats->total_entries],
                ['Expired Entries', $stats->expired_entries],
                ['Active Entries', $stats->total_entries - $stats->expired_entries],
                ['Total Key Size', $this->formatBytes($stats->total_key_size)],
                ['Total Value Size', $this->formatBytes($stats->total_value_size)],
                ['Average Time to Expire', round($stats->avg_seconds_to_expire / 3600, 2) . ' hours'],
            ]);
            
        } catch (\Exception $e) {
            $this->error("Failed to get database cache stats: " . $e->getMessage());
        }
    }
    
    protected function monitorFileCache()
    {
        $path = config('cache.stores.file.path', storage_path('framework/cache/data'));
        
        if (!is_dir($path)) {
            $this->error("Cache directory not found: {$path}");
            return;
        }
        
        $files = glob($path . '/*');
        $totalSize = 0;
        $fileCount = 0;
        
        foreach ($files as $file) {
            if (is_file($file)) {
                $totalSize += filesize($file);
                $fileCount++;
            }
        }
        
        $this->info("File Cache Information:");
        $this->table(['Metric', 'Value'], [
            ['Cache Directory', $path],
            ['Total Files', $fileCount],
            ['Total Size', $this->formatBytes($totalSize)],
            ['Average File Size', $this->formatBytes($fileCount > 0 ? $totalSize / $fileCount : 0)],
        ]);
    }
    
    protected function showDetailedCacheInfo()
    {
        $this->info("\nDetailed Cache Information:");
        $this->info("===========================");
        
        // Show cache configuration
        $config = config('cache');
        $this->info("\nCache Configuration:");
        $this->table(['Setting', 'Value'], [
            ['Default Driver', $config['default']],
            ['Cache Prefix', $config['prefix']],
            ['Available Stores', implode(', ', array_keys($config['stores']))],
        ]);
        
        // Test cache operations
        $this->info("\nCache Performance Test:");
        
        $start = microtime(true);
        Cache::put('test_key', str_repeat('x', 1024), 10); // 1KB data
        $writeTime = microtime(true) - $start;
        
        $start = microtime(true);
        $value = Cache::get('test_key');
        $readTime = microtime(true) - $start;
        
        $this->table(['Operation', 'Time'], [
            ['Write (1KB)', round($writeTime * 1000, 2) . 'ms'],
            ['Read (1KB)', round($readTime * 1000, 2) . 'ms'],
        ]);
        
        Cache::forget('test_key');
    }
    
    protected function formatBytes($bytes, $precision = 2)
    {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        
        $bytes = max($bytes, 0);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);
        
        $bytes /= pow(1024, $pow);
        
        return round($bytes, $precision) . ' ' . $units[$pow];
    }
}

Testing Caching

Cache Testing

<?php
// tests/Feature/CacheTest.php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Support\Facades\Cache;
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\Models\Product;
use App\Models\User;

class CacheTest extends TestCase
{
    use RefreshDatabase;

    public function test_cache_put_and_get()
    {
        Cache::put('test_key', 'test_value', 60);
        
        $this->assertEquals('test_value', Cache::get('test_key'));
    }
    
    public function test_cache_remember()
    {
        $value = Cache::remember('expensive_calculation', 60, function () {
            return 'calculated_value';
        });
        
        $this->assertEquals('calculated_value', $value);
        $this->assertTrue(Cache::has('expensive_calculation'));
    }
    
    public function test_product_cache()
    {
        $product = Product::factory()->create();
        
        // Cache product
        Cache::remember("product:{$product->id}", 3600, function () use ($product) {
            return $product;
        });
        
        // Retrieve from cache
        $cachedProduct = Cache::get("product:{$product->id}");
        
        $this->assertNotNull($cachedProduct);
        $this->assertEquals($product->id, $cachedProduct->id);
        $this->assertEquals($product->name, $cachedProduct->name);
    }
    
    public function test_cache_tags()
    {
        Cache::tags(['users', 'products'])->put('test_key', 'test_value', 60);
        
        $this->assertEquals('test_value', Cache::tags(['users', 'products'])->get('test_key'));
        
        // Clear by tag
        Cache::tags(['users'])->flush();
        $this->assertNull(Cache::tags(['users', 'products'])->get('test_key'));
    }
    
    public function test_cache_increment_decrement()
    {
        Cache::put('counter', 0, 60);
        
        Cache::increment('counter');
        $this->assertEquals(1, Cache::get('counter'));
        
        Cache::increment('counter', 4);
        $this->assertEquals(5, Cache::get('counter'));
        
        Cache::decrement('counter', 2);
        $this->assertEquals(3, Cache::get('counter'));
    }
    
    public function test_cache_lock()
    {
        $lock = Cache::lock('test_lock', 10);
        
        $this->assertTrue($lock->get());
        $this->assertFalse($lock->get()); // Should not get lock again
        
        $lock->release();
        
        $this->assertTrue($lock->get()); // Should get lock after release
        $lock->release();
    }
    
    public function test_cache_flush()
    {
        Cache::put('key1', 'value1', 60);
        Cache::put('key2', 'value2', 60);
        
        $this->assertTrue(Cache::has('key1'));
        $this->assertTrue(Cache::has('key2'));
        
        Cache::flush();
        
        $this->assertFalse(Cache::has('key1'));
        $this->assertFalse(Cache::has('key2'));
    }
    
    public function test_cache_with_fake()
    {
        Cache::fake();
        
        Cache::put('key', 'value', 60);
        
        // Assert cache was "put"
        Cache::assertPut('key', 'value', 60);
        
        // Assert cache was "put" with closure
        Cache::assertPut('key', function ($value) {
            return $value === 'value';
        }, 60);
        
        // Assert nothing in cache
        Cache::assertNothingWritten();
    }
}

Best Practices and Common Pitfalls

Cache Invalidation Strategies

<?php
// app/Services/CacheInvalidationService.php

namespace App\Services;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Event;
use App\Models\Product;
use App\Models\User;
use App\Models\Order;

class CacheInvalidationService
{
    public function registerEventListeners()
    {
        // Invalidate cache when product is updated
        Event::listen('eloquent.updated: ' . Product::class, function ($product) {
            $this->invalidateProductCache($product);
        });
        
        Event::listen('eloquent.deleted: ' . Product::class, function ($product) {
            $this->invalidateProductCache($product);
        });
        
        // Invalidate cache when user is updated
        Event::listen('eloquent.updated: ' . User::class, function ($user) {
            $this->invalidateUserCache($user);
        });
        
        // Invalidate cache when order is created/updated
        Event::listen('eloquent.created: ' . Order::class, function ($order) {
            $this->invalidateOrderCache($order);
        });
        
        Event::listen('eloquent.updated: ' . Order::class, function ($order) {
            $this->invalidateOrderCache($order);
        });
    }
    
    public function invalidateProductCache(Product $product)
    {
        // Clear specific product cache
        Cache::forget("product:{$product->id}:full");
        Cache::forget("product:{$product->id}:basic");
        Cache::forget("product:{$product->id}:variants");
        
        // Clear product lists
        Cache::forget('products:popular');
        Cache::forget('products:featured');
        Cache::forget('products:new');
        Cache::forget('products:category:' . $product->category_id);
        
        // Clear related caches
        Cache::forget('stats:products:global');
        Cache::tags(['products'])->flush();
        
        // Clear search indexes if using
        if (Cache::has('search:products:index')) {
            Cache::forget('search:products:index');
        }
    }
    
    public function invalidateUserCache(User $user)
    {
        Cache::forget("user:{$user->id}:profile");
        Cache::forget("user:{$user->id}:orders");
        Cache::forget("user:{$user->id}:stats");
        
        // Clear user lists if this user is in them
        $this->invalidateUserLists();
        
        Cache::tags(['users', "user:{$user->id}"])->flush();
    }
    
    public function invalidateOrderCache(Order $order)
    {
        Cache::forget("order:{$order->id}:full");
        Cache::forget("order:{$order->id}:basic");
        
        // Clear user's order list
        Cache::forget("user:{$order->user_id}:orders");
        
        // Clear order statistics
        Cache::forget('stats:orders:daily');
        Cache::forget('stats:orders:monthly');
        Cache::forget('stats:revenue:daily');
        
        Cache::tags(['orders'])->flush();
    }
    
    protected function invalidateUserLists()
    {
        Cache::forget('users:top:spenders');
        Cache::forget('users:top:active');
        Cache::forget('users:recent');
    }
    
    public function clearStaleCache($olderThanHours = 24)
    {
        // This is more relevant for file/database cache
        $cachePath = storage_path('framework/cache/data');
        
        if (is_dir($cachePath)) {
            $files = glob($cachePath . '/*');
            $now = time();
            
            foreach ($files as $file) {
                if (is_file($file)) {
                    // Delete files older than specified hours
                    if ($now - filemtime($file) >= $olderThanHours * 3600) {
                        unlink($file);
                    }
                }
            }
        }
    }
}

Common Interview Questions & Answers

  1. What is caching and why is it important?
    Caching stores frequently accessed data in memory for faster retrieval. It reduces database load, improves response times, and helps applications scale to handle more traffic.
  2. What cache drivers does Laravel support?
    Laravel supports file, database, Redis, Memcached, APC, array (for testing), DynamoDB, and Octane drivers.
  3. What's the difference between Redis and Memcached?
    Redis supports more data structures (strings, hashes, lists, sets, sorted sets), persistence, replication, and transactions. Memcached is simpler, multi-threaded, and can be faster for simple key-value storage.
  4. How do cache tags work?
    Cache tags allow you to assign tags to cached items and flush all items with a specific tag. Useful for grouping related cache items.
  5. What is cache stampede and how to prevent it?
    Cache stampede occurs when many requests try to regenerate the same cache item simultaneously after expiration. Prevent with cache locks, early regeneration, or probabilistic expiration.
  6. How do you handle cache invalidation?
    Use event listeners to invalidate cache when data changes, implement TTL (time-to-live), version cache keys, or use cache tags for batch invalidation.
  7. What's the purpose of cache locks?
    Cache locks prevent concurrent access to shared resources, useful for preventing race conditions in cache regeneration or ensuring only one process executes a task.
  8. How can you monitor cache performance?
    Monitor hit/miss ratios, memory usage, eviction rates, and response times. Use commands like cache:monitor or Redis/Memcached monitoring tools.
  9. What are some common caching strategies?
    Cache-aside (lazy loading)
    Write-through
    Write-behind
    Refresh-ahead
    Time-based expiration
    Event-based invalidation
  10. How do you test caching?
    Use Cache::fake() for testing, assert cache operations were performed, and test cache invalidation scenarios.

Performance Optimization Tips

  • Use appropriate TTLs: Different data needs different expiration times
  • Implement cache warming: Pre-load frequently accessed data
  • Use cache tags wisely: They have performance overhead in Redis
  • Monitor cache hit ratios: Aim for >90% hit rate
  • Use compression: For large cache items
  • Implement fallback strategies: When cache fails
  • Use different cache stores: For different types of data
  • Regularly clean stale cache: Especially for file/database drivers

You've now mastered Laravel caching! From basic operations to advanced Redis features and performance optimization, you have the complete toolkit to dramatically improve your application's performance and scalability.