0

Optimizing Eloquent — select only needed columns, avoid *

Intermediate5 min read·lv-27-005
sqlperformanceinterview

Concept

Performance optimization in Laravel addresses the most common bottlenecks: database queries, caching, session performance, asset compilation, and OPcache configuration.

Database:

  • Add indexes on every column used in WHERE, ORDER BY, JOIN ON. Check slow query logs.
  • Use select() to only fetch needed columns — avoid SELECT *.
  • chunk() or lazy() for bulk operations instead of loading everything into memory.
  • withCount() instead of fetching all records to count.
  • preventLazyLoading() in development to catch N+1 before production.
  • Read replicas for read-heavy apps: DB::connection('mysql-read')->table(...).

Caching:

  • Cache expensive queries with Cache::remember().
  • Cache config and routes: php artisan config:cache && php artisan route:cache.
  • Cache views: php artisan view:cache.
  • Use php artisan optimize (runs config, route, view, event caching).
  • In production, always run artisan optimize after deploy.

Session: Use database or redis session driver in production. file driver is the default but doesn't scale horizontally.

Queue workers: Move slow operations (email, image processing) to queues. Never block the HTTP request on slow I/O.

OPcache: Ensure PHP OPcache is enabled in production (opcache.enable=1). Laravel's bootloader benefits significantly from opcode caching.

Octane: Laravel Octane (Swoole or RoadRunner) keeps the application in memory between requests. Massive throughput improvement for read-heavy APIs.

Code Example

php
<?php
// Bad: N+1, SELECT *, loads all into memory
$posts = Post::all(); // loads ALL posts
foreach ($posts as $post) {
    echo $post->author->name; // N queries!
    echo count($post->comments); // N more queries!
}

// Good: indexed columns, select only needed, eager load
$posts = Post::select(['id', 'title', 'user_id', 'published_at'])
    ->with(['author:id,name']) // 1 extra query for all authors
    ->withCount('comments')    // 1 extra query, adds comments_count attribute
    ->published()
    ->orderByDesc('published_at')
    ->paginate(20);

// Cache expensive aggregates
$stats = \Illuminate\Support\Facades\Cache::remember('dashboard:stats', 3600, function() {
    return [
        'total_users'  => \App\Models\User::count(),
        'active_posts' => \App\Models\Post::published()->count(),
        'revenue'      => \App\Models\Order::sum('total'),
    ];
});

// Cache per-user
$userFeed = \Illuminate\Support\Facades\Cache::remember(
    "user:{$userId}:feed",
    300, // 5 minutes
    fn() => Post::forUser($userId)->with('author')->limit(20)->get()
);

// Production deploy checklist
// php artisan config:cache    — merge all config files into one cached file
// php artisan route:cache     — compile all routes into a single file
// php artisan view:cache      — pre-compile all Blade views
// php artisan event:cache     — cache event/listener map
// composer install --optimize-autoloader --no-dev  — optimize Composer autoloader
// php artisan optimize        — runs config + route + view + event cache together

// Memory-efficient bulk processing
Post::where('status', 'draft')->chunk(200, function($posts) {
    foreach ($posts as $post) {
        // process without loading all 100,000 posts at once
    }
});