laravel-performance

Auto-invoked skill

Optimize with caching, Pulse monitoring, memory management

Trigger Keywords

This skill automatically activates when Claude detects these keywords:

slow optimize cache pulse performance memory Big O O(n) complexity nested loop

Overview

The laravel-performance skill provides expertise for optimizing Laravel applications. It covers caching strategies, query optimization, memory management, and monitoring with Laravel Pulse.

What This Skill Provides

  • Caching Strategies - Data, query, and full-page caching
  • Query Optimization - N+1 fixes, indexing, eager loading
  • Big O Fixes - Detect O(n²) patterns and fix with O(1) lookups
  • Memory Management - Chunking, lazy collections, memory limits
  • Laravel Pulse - Real-time performance monitoring
  • Profiling - Identifying bottlenecks with tools
  • Asset Optimization - Vite bundling, compression

Example Conversations

# Diagnosing slowness
"My dashboard page is loading slowly, how can I profile it?"

# Caching
"What's the best way to cache expensive database queries?"

# Memory issues
"My job runs out of memory processing large datasets"

# Monitoring
"Set up Laravel Pulse to monitor my application performance"

Caching Patterns

<?php

use Illuminate\Support\Facades\Cache;

// Basic cache with TTL
$users = Cache::remember('active-users', 3600, function () {
    return User::where('active', true)->get();
});

// Cache tags for group invalidation
$products = Cache::tags(['products', 'catalog'])->remember(
    "category-{$categoryId}-products",
    3600,
    fn () => Product::where('category_id', $categoryId)->get()
);

// Invalidate all products cache
Cache::tags('products')->flush();

// Atomic locks for expensive operations
$result = Cache::lock('report-generation', 120)->block(10, function () {
    return $this->generateExpensiveReport();
});

// Cache model queries efficiently
class Product extends Model
{
    public static function getCachedFeatured()
    {
        return Cache::remember('featured-products', 3600, function () {
            return static::where('featured', true)
                ->with('category')
                ->limit(10)
                ->get();
        });
    }

    protected static function booted()
    {
        static::saved(fn () => Cache::forget('featured-products'));
        static::deleted(fn () => Cache::forget('featured-products'));
    }
}

Memory-Efficient Processing

<?php

// BAD: Loads all records into memory
$users = User::all();
foreach ($users as $user) {
    $user->sendNewsletter();
}

// GOOD: Process in chunks
User::chunk(1000, function ($users) {
    foreach ($users as $user) {
        $user->sendNewsletter();
    }
});

// BETTER: Lazy collection (one record at a time)
User::lazy()->each(function ($user) {
    $user->sendNewsletter();
});

// For cursor-based iteration (lowest memory)
foreach (User::cursor() as $user) {
    $user->sendNewsletter();
}

// Chunk by ID for stability during updates
User::chunkById(1000, function ($users) {
    foreach ($users as $user) {
        $user->update(['processed_at' => now()]);
    }
});

Query Optimization

<?php

// BAD: N+1 queries
$posts = Post::all();
foreach ($posts as $post) {
    echo $post->author->name; // Query for each post!
}

// GOOD: Eager loading
$posts = Post::with('author')->get();

// Select only needed columns
$posts = Post::select('id', 'title', 'user_id')
    ->with('author:id,name')
    ->get();

// Use query scopes for reusability
class Post extends Model
{
    public function scopeWithAuthorName($query)
    {
        return $query->select('id', 'title', 'user_id')
            ->with('author:id,name');
    }
}

// Index frequently queried columns
// In migration:
$table->index(['status', 'created_at']); // Composite index
$table->index('user_id');

// Use explain to analyze queries
DB::enableQueryLog();
$posts = Post::where('status', 'published')->get();
dd(DB::getQueryLog());

Big O Complexity Fixes

Big O complexity issues cause exponential slowdowns as data grows.

<?php

// BAD: O(n²) - Nested loops
foreach ($users as $user) {
    foreach ($orders as $order) {
        if ($order->user_id === $user->id) {
            // Process - runs n×m times!
        }
    }
}

// GOOD: O(n) - Use relationships or keyBy
$users = User::with('orders')->get();
// Or: $ordersByUser = $orders->groupBy('user_id');

// BAD: O(n²) - contains() in loop
foreach ($newUsers as $userData) {
    if (!$existingEmails->contains($userData['email'])) {
        User::create($userData);
    }
}

// GOOD: O(n) - Use flip() for O(1) lookup
$existingEmails = User::pluck('email')->flip();
foreach ($newUsers as $userData) {
    if (!$existingEmails->has($userData['email'])) {
        User::create($userData);
    }
}

Laravel Pulse Setup

# Install Pulse
composer require laravel/pulse

# Publish config and migrations
php artisan vendor:publish --provider="Laravel\Pulse\PulseServiceProvider"
php artisan migrate

# Access at /pulse (protected by gate)
// config/pulse.php - Configure recorders
'recorders' => [
    \Laravel\Pulse\Recorders\CacheInteractions::class => [
        'enabled' => true,
        'sample_rate' => 1,
    ],
    \Laravel\Pulse\Recorders\Exceptions::class => [
        'enabled' => true,
    ],
    \Laravel\Pulse\Recorders\Queues::class => [
        'enabled' => true,
    ],
    \Laravel\Pulse\Recorders\SlowQueries::class => [
        'enabled' => true,
        'threshold' => 100, // ms
    ],
    \Laravel\Pulse\Recorders\SlowRequests::class => [
        'enabled' => true,
        'threshold' => 1000, // ms
    ],
],

// app/Providers/AppServiceProvider.php - Gate access
use Laravel\Pulse\Facades\Pulse;

public function boot()
{
    Pulse::authorize(function ($request) {
        return $request->user()?->isAdmin();
    });
}

Performance Checklist

Area Optimization
Config config:cache, route:cache, view:cache
Queries Eager load, indexes, select specific columns
Big O Avoid nested loops, use keyBy/groupBy for O(1) lookups
Caching Redis for sessions, cache, and queues
Assets Vite minification, CDN, compression
PHP OPcache enabled, JIT compilation

Common Pitfalls

  • Caching forever - Always set TTL, stale data causes bugs
  • Cache stampede - Use atomic locks for expensive operations
  • Over-eager loading - Only load relationships you need
  • Missing indexes - Add indexes for WHERE, ORDER BY columns
  • O(n²) nested loops - Use groupBy() or keyBy() for O(1) lookups
  • contains() in loops - Use flip()->has() instead for O(1) lookups

Profiling Tools

// Laravel Debugbar (development)
composer require barryvdh/laravel-debugbar --dev

// Clockwork (development)
composer require itsgoingd/clockwork --dev

// Custom timing
$start = microtime(true);
// ... operation
$duration = microtime(true) - $start;
Log::info("Operation took {$duration}s");

// Memory usage
$memory = memory_get_peak_usage(true) / 1024 / 1024;
Log::info("Peak memory: {$memory}MB");

Related Commands

Related Skills