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 — avoidSELECT *. chunk()orlazy()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 optimizeafter 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
}
});