Hindi
WorkExperienceAboutBlogGitHubContactContact
Back to Blog
LaravelPerformancePHPBackendOptimization

Laravel Performance Optimization: 10 Proven Techniques

RD

Raman Daksh

July 4, 2025 · 11 min read

Performance optimization is what separates a Laravel app that survives traffic spikes from one that crumbles. After optimizing dozens of Laravel applications — some handling 10,000+ requests per minute — here are the 10 techniques that make the biggest difference.

1. Measure Before You Optimize

Never guess where the bottleneck is. Always measure first:

composer require laravel/telescope --dev
php artisan telescope:install
php artisan migrate

Telescope shows you every request, query, exception, log, and job. Sort by "slowest queries" — that's your starting point. In production, use a combination of:

  • **Laravel Pulse** (real-time monitoring, free in 11.x)
  • **New Relic/AppSignal** (paid, but better for historical analysis)
  • **Database query logs** (MySQL slow query log)
  • **The 80/20 rule**: 80% of performance issues come from 20% of the code — usually database queries and N+1 problems.

    2. Eradicate N+1 Queries

    This is the single most common performance killer in Laravel apps:

    // BAD — N+1: 1 query for posts + 100 queries for users
    $posts = Post::latest()->take(100)->get();
    foreach ($posts as $post) {
        echo $post->user->name;
    }
    
    // GOOD — 2 queries total
    $posts = Post::with('user')->latest()->take(100)->get();

    Use Laravel's **lazy loading prevention** to catch these automatically:

    // AppServiceProvider.php
    use IlluminateDatabaseEloquentModel;
    
    Model::preventLazyLoading(!app()->isProduction());

    This throws an exception when you access a relationship that wasn't eager-loaded. In 2025, there's no excuse for N+1 in production.

    3. Redis Caching Strategy

    Cache aggressively. Not just full-page cache — strategic caching at multiple levels:

    // Level 1: Query result caching
    $products = Cache::remember('products.active', 3600, function () {
        return Product::where('active', true)
            ->with('category')
            ->get();
    });
    
    // Level 2: Fragment caching in Blade
    @cache('user.profile.' . $user->id, 300)
        <div class="profile-card">
            {{ $user->bio }}
            {{ $user->recent_activity }}
        </div>
    @endcache
    
    // Level 3: Full-page cache for anonymous traffic
    // Use Laravel Page Cache or Varnish

    **Cache invalidation is the hard part.** Use model events to invalidate intelligently:

    class Product extends Model
    {
        protected static function booted()
        {
            static::saved(fn () => Cache::forget('products.active'));
            static::deleted(fn () => Cache::forget('products.active'));
        }
    }

    4. Queue Heavy Work

    If your HTTP response takes more than 200ms, offload work to queues:

    // BAD — everything in the request cycle
    class OrderController
    {
        public function store(Request $request)
        {
            $order = Order::create($request->validated());
            $this->processPayment($order);       // 500ms
            $this->sendEmail($order);            // 300ms
            $this->updateInventory($order);      // 200ms
            return response()->json($order);     // Total: 1s+
        }
    }
    
    // GOOD — dispatch to queues
    class OrderController
    {
        public function store(Request $request)
        {
            $order = Order::create($request->validated());
            ProcessPayment::dispatch($order);    // 5ms
            SendOrderEmail::dispatch($order);    // 5ms
            UpdateInventory::dispatch($order);   // 5ms
            return response()->json($order);     // Total: 50ms
        }
    }

    Configure queue workers for high throughput:

    # Supervisor config — multiple workers per queue
    [program:laravel-worker]
    process_name=%(program_name)s_%(process_num)02d
    command=php /var/www/artisan queue:work redis --queue=high,default --sleep=3 --tries=3 --max-time=3600
    numprocs=8
    autostart=true
    autorestart=true

    5. Database Indexing Strategy

    Indexes are your first line of defense. Check your slow query log:

    -- Always index foreign keys
    CREATE INDEX idx_orders_user_id ON orders(user_id);
    
    -- Composite indexes for frequent WHERE + ORDER BY combinations
    CREATE INDEX idx_orders_status_created ON orders(status, created_at);
    
    -- Covering indexes for specific queries
    CREATE INDEX idx_products_active_price ON products(active, price) INCLUDE (name, slug);

    Use Laravel's `explain()` to verify query plans:

    DB::table('orders')
        ->where('status', 'processing')
        ->orderBy('created_at', 'desc')
        ->explain()
        ->dd();

    6. Octane: The Game-Changer

    Laravel Octane supercharges your application by using Swoole or RoadRunner:

    composer require laravel/octane
    php artisan octane:install
    php artisan octane:start --server=swoole --workers=8 --max-requests=1000

    | Metric | Without Octane | With Octane |

    | Requests/sec | 300 | 4,200 |

    | Memory/request | 18MB | 2MB (shared) |

    | Concurrent users (same server) | 500 | 10,000+ |

    Octane boots your app once and keeps it in memory. Every request reuses the same bootstrapped application. The result is 10-15x throughput improvement.

    **Watch out for**: memory leaks. Octane requires clean code — static properties, singletons, and global state can leak between requests. Test thoroughly.

    7. Optimize Asset Delivery

    # Minify and version assets
    php artisan filament:optimize  # if using Filament
    npm run build                   # Vite bundles and minifies
    
    # Enable Brotli/Gzip compression (nginx)
    gzip on;
    gzip_types application/json text/css application/javascript;
    gzip_comp_level 6;
    
    # Cache control headers
    location ~* .(css|js|jpg|png|webp)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    8. Config and Route Caching

    Never skip these in production:

    php artisan config:cache     # Merge all config into one file
    php artisan route:cache      # Pre-compile route registrations
    php artisan view:cache       # Pre-compile Blade templates
    php artisan event:cache      # Cache event discovery

    Each reduces load time by 10-50ms. Combined, they can save 100ms+ per request.

    9. Database Connection Pooling

    For high-traffic apps, database connections are a bottleneck:

    // config/database.php — use persistent connections
    'mysql' => [
        'driver' => 'mysql',
        'host' => env('DB_HOST'),
        'database' => env('DB_DATABASE'),
        'username' => env('DB_USERNAME'),
        'password' => env('DB_PASSWORD'),
        'options' => [
            PDO::ATTR_PERSISTENT => true,  // Reuse connections
        ],
    ],

    For even better performance, use a connection pooler like **PgBouncer** (PostgreSQL) or **ProxySQL** (MySQL).

    10. Monitoring and Alerting

    You can't fix what you don't see:

    // config/services.php — set up real alerts
    'alerts' => [
        'slack_webhook' => env('ALERT_SLACK_WEBHOOK'),
        'email' => env('ALERT_EMAIL'),
    ],
    
    // Define alert thresholds in AppServiceProvider
    public function boot()
    {
        // Alert if queue backlog exceeds 1000
        Queue::looping(function () {
            $size = Queue::size();
            if ($size > 1000) {
                Notification::route('slack', config('services.alerts.slack_webhook'))
                    ->notify(new QueueBackupAlert($size));
            }
        });
    }

    Performance Checklist

  • [ ] Enable lazy loading prevention
  • [ ] Run Telescope to identify slow queries
  • [ ] Fix all N+1 queries (check debugbar)
  • [ ] Add Redis for cache and sessions
  • [ ] Configure queue workers with Supervisor
  • [ ] Implement Octane for high-traffic routes
  • [ ] Cache config, routes, and views
  • [ ] Add database indexes from slow query log
  • [ ] Enable Gzip/Brotli and cache headers
  • [ ] Set up monitoring alerts
  • Optimization is iterative. Start with the database, add caching, then move to application-level improvements. The goal isn't perfection — it's making your app fast enough that performance is never the bottleneck.

    All Posts