Laravel Performance Improvements: A Technical Guide
Laravel continues to evolve with each release, bringing significant performance improvements that can dramatically enhance your application’s speed and efficiency. In this technical guide, we’ll explore the latest performance optimizations in Laravel 10 and 11, complete with practical code examples you can implement today.
1. Optimized Eager Loading: Eliminating Unnecessary Queries
One of the most impactful performance improvements is the optimization of eager loading. Laravel now intelligently skips queries when there are no related records to load, preventing wasteful database calls.
The N+1 Problem - Before
// Bad: This creates N+1 queries
$posts = Post::all();
foreach ($posts as $post) {
echo $post->author->name; // Each iteration triggers a query
}
Optimized Eager Loading - After
// Good: Uses optimized eager loading
$posts = Post::with(['author', 'comments.user', 'tags'])->get();
foreach ($posts as $post) {
echo $post->author->name; // No additional queries
foreach ($post->comments as $comment) {
echo $comment->user->name; // Still no queries
}
}
Lazy Eager Loading for Conditional Relationships
$posts = Post::all();
if ($shouldLoadComments) {
$posts->load('comments'); // Load only when needed
}
// Even more granular control with loadMissing
$posts->loadMissing(['author', 'tags']);
2. Route Caching: 10x Faster Route Registration
Route caching has been significantly improved, reducing route registration time dramatically. This is especially important for applications with hundreds of routes.
Enable Route Caching in Production
php artisan route:cache
Benchmark Example
// config/app.php - Enable route caching performance monitoring
'debug_route_loading' => env('DEBUG_ROUTE_LOADING', false),
// In a service provider for benchmarking
public function boot()
{
if (config('app.debug_route_loading')) {
$start = microtime(true);
// After routes are loaded
app()->booted(function () use ($start) {
$time = (microtime(true) - $start) * 1000;
Log::info("Routes loaded in {$time}ms");
});
}
}
Optimized Route Definitions
// routes/api.php
// Group routes with shared middleware for better performance
Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () {
Route::apiResource('posts', PostController::class);
Route::apiResource('comments', CommentController::class);
Route::apiResource('users', UserController::class);
});
// Use route model binding with explicit keys
Route::get('/posts/{post:slug}', [PostController::class, 'show']);
3. Database Query Optimization with Strict Mode
Laravel now includes strict mode for Eloquent, which helps identify performance issues during development.
Enable Strict Mode
// app/Providers/AppServiceProvider.php
use Illuminate\Database\Eloquent\Model;
public function boot()
{
// Prevent lazy loading in development
Model::preventLazyLoading(!app()->isProduction());
// Prevent silently discarding attributes
Model::preventSilentlyDiscardingAttributes(!app()->isProduction());
// Prevent accessing missing attributes
Model::preventAccessingMissingAttributes(!app()->isProduction());
}
Optimized Query Building
// Select only needed columns
$users = User::select(['id', 'name', 'email'])
->where('active', true)
->get();
// Use chunk for large datasets
User::where('active', true)->chunk(1000, function ($users) {
foreach ($users as $user) {
// Process in memory-efficient batches
}
});
// Lazy collections for massive datasets
User::where('active', true)->lazy()->each(function ($user) {
// Memory usage stays constant regardless of dataset size
});
4. Config and View Caching
Caching configuration and views can provide substantial performance gains in production.
# Cache all configuration files
php artisan config:cache
# Cache all Blade views
php artisan view:cache
# Cache events and listeners
php artisan event:cache
# Clear all caches when deploying
php artisan optimize:clear
php artisan optimize
Deployment Script Example
#!/bin/bash
# deploy.sh
cd /var/www/laravel
# Pull latest code
git pull origin main
# Install dependencies
composer install --no-dev --optimize-autoloader
# Run migrations
php artisan migrate --force
# Clear and rebuild caches
php artisan optimize:clear
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
# Restart queue workers
php artisan queue:restart
echo "Deployment complete!"
5. Queue Performance with Job Batching
Laravel’s job batching has been optimized for better throughput and monitoring capabilities.
use Illuminate\Bus\Batch;
use Illuminate\Support\Facades\Bus;
use App\Jobs\ProcessPodcast;
use App\Jobs\OptimizePodcast;
use App\Jobs\PublishPodcast;
// Create a batch of jobs
$batch = Bus::batch([
new ProcessPodcast($podcast),
new OptimizePodcast($podcast),
new PublishPodcast($podcast),
])
->then(function (Batch $batch) {
// All jobs completed successfully
Log::info('Batch completed: ' . $batch->id);
})
->catch(function (Batch $batch, Throwable $e) {
// Handle batch failure
Log::error('Batch failed: ' . $e->getMessage());
})
->finally(function (Batch $batch) {
// Cleanup regardless of success/failure
})
->allowFailures()
->dispatch();
// Monitor batch progress
$batch = Bus::findBatch($batchId);
echo "Progress: {$batch->progress()}%";
echo "Pending Jobs: {$batch->pendingJobs}";
Chainable Jobs for Sequential Processing
Bus::chain([
new ProcessPayment($order),
new SendReceipt($order),
new UpdateInventory($order),
new NotifyWarehouse($order),
])->dispatch();
6. Memory-Efficient Collection Processing
Laravel Collections now include lazy evaluation capabilities for handling large datasets without exhausting memory.
use Illuminate\Support\LazyCollection;
// Process a large CSV file
LazyCollection::make(function () {
$handle = fopen('large-file.csv', 'r');
while ($line = fgetcsv($handle)) {
yield $line;
}
fclose($handle);
})
->skip(1) // Skip header row
->chunk(1000)
->each(function ($chunk) {
// Process 1000 records at a time
DB::table('imports')->insert($chunk->toArray());
});
// Cursor for database queries
User::where('active', true)
->cursor() // Returns LazyCollection
->filter(fn ($user) => $user->shouldBeNotified())
->each(fn ($user) => $user->notify(new WeeklyDigest));
7. Native Type Declarations for Better Performance
Laravel 10+ uses native PHP type declarations, enabling better opcache optimization and static analysis.
<?php
namespace App\Services;
use App\Models\User;
use App\DTOs\UserData;
use Illuminate\Support\Collection;
class UserService
{
public function __construct(
private readonly UserRepository $repository,
private readonly CacheService $cache,
) {}
public function findById(int $id): ?User
{
return $this->cache->remember(
key: "user.{$id}",
ttl: 3600,
callback: fn () => $this->repository->find($id)
);
}
public function getActiveUsers(): Collection
{
return User::where('active', true)
->select(['id', 'name', 'email'])
->get();
}
public function createUser(UserData $data): User
{
return User::create([
'name' => $data->name,
'email' => $data->email,
'password' => bcrypt($data->password),
]);
}
}
8. Redis Cache Optimization
Optimizing your cache configuration can yield significant performance improvements.
// config/database.php
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'), // phpredis is faster than predis
'default' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_DB', 0),
'read_timeout' => 60,
'persistent' => true, // Keep connections alive
],
'cache' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_CACHE_DB', 1),
],
],
Efficient Cache Usage Patterns
use Illuminate\Support\Facades\Cache;
// Cache with tags for granular invalidation
Cache::tags(['posts', 'users'])->put('user.1.posts', $posts, 3600);
// Invalidate all post caches
Cache::tags(['posts'])->flush();
// Atomic locks for race condition prevention
$lock = Cache::lock('process-payment-' . $orderId, 10);
if ($lock->get()) {
try {
// Process payment safely
$this->processPayment($order);
} finally {
$lock->release();
}
}
// Remember with flexible cache driver
$value = Cache::flexible('key', [300, 600], function () {
return $this->expensiveOperation();
});
9. Optimized Middleware Stack
Reduce middleware overhead by organizing and optimizing your middleware.
// app/Http/Kernel.php
protected $middlewareGroups = [
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
// Use middleware priority to control execution order
protected $middlewarePriority = [
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\Auth\Middleware\Authenticate::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];
Skip Middleware When Not Needed
// In controller
public function __construct()
{
$this->middleware('auth')->except(['index', 'show']);
$this->middleware('throttle:60,1')->only(['store', 'update']);
}
10. Database Connection Pooling
For high-traffic applications, connection pooling dramatically reduces database overhead.
// config/database.php
'mysql' => [
'driver' => 'mysql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
// Connection pooling options
'options' => [
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_EMULATE_PREPARES => false,
],
],
Benchmarking Your Improvements
Always measure before and after implementing optimizations:
use Illuminate\Support\Benchmark;
// Simple benchmark
$time = Benchmark::measure(fn () => User::all());
echo "Query took: {$time}ms";
// Compare multiple approaches
Benchmark::dd([
'Eager Loading' => fn () => Post::with('author')->get(),
'Lazy Loading' => fn () => Post::all()->each->author,
'Join Query' => fn () => Post::join('users', 'posts.user_id', '=', 'users.id')->get(),
]);
Conclusion
These performance improvements in Laravel 10 and 11 provide developers with powerful tools to build faster, more efficient applications. By implementing eager loading optimization, route caching, strict mode, queue batching, and the other techniques covered here, you can significantly reduce response times and server resource usage.
Remember to always benchmark your changes and test in a production-like environment. Performance optimization is an iterative process—measure, implement, and measure again.
Happy coding!