Laravel Performance Optimization: Scaling to Million Users in 2025
Building Laravel applications that can handle millions of users requires a deep understanding of performance optimization techniques. In this comprehensive guide, we'll explore cutting-edge strategies that are essential for high-traffic applications in 2025.
The Performance-First Mindset
Performance optimization isn't just about making things faster—it's about creating sustainable, scalable applications that provide excellent user experiences even under heavy load.
Key Performance Metrics to Track:
- Response time (aim for <200ms)
- Database query count and execution time
- Memory usage
- Cache hit rates
- Queue processing time
- Error rates
Laravel Octane: The Game Changer
Laravel Octane supercharges your application performance by keeping it loaded in memory:
Installation and Setup
composer require laravel/octane
php artisan octane:install --server=roadrunner
Octane Configuration
// config/octane.php
return [
'server' => env('OCTANE_SERVER', 'roadrunner'),
'https' => env('OCTANE_HTTPS', false),
'workers' => env('OCTANE_WORKERS', 4),
'max_requests' => env('OCTANE_MAX_REQUESTS', 500),
'rr_config' => base_path('rr.yaml'),
];
Memory Leak Prevention
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Laravel\Octane\Facades\Octane;
class ClearMemoryLeaks
{
public function handle(Request $request, Closure $next)
{
$response = $next($request);
// Clear potential memory leaks
if (Octane::hasListeners()) {
Octane::flushState();
}
return $response;
}
}
Advanced Database Optimization
1. Query Optimization with Indexes
// Migration for optimized blog queries
Schema::table('blogs', function (Blueprint $table) {
$table->index(['status', 'published_at']);
$table->index(['author_id', 'created_at']);
$table->fullText(['title', 'content']);
});
// Optimized query
$blogs = Blog::select(['id', 'title', 'excerpt', 'published_at'])
->where('status', 'published')
->with(['author:id,name'])
->latest('published_at')
->paginate(15);
2. Database Connection Optimization
// 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',
'options' => [
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false,
],
'pool' => [
'min_connections' => 1,
'max_connections' => 10,
'connect_timeout' => 10,
'wait_timeout' => 3,
'heartbeat' => -1,
'max_idle_time' => 60,
],
],
3. Read/Write Database Splitting
'mysql' => [
'read' => [
'host' => [
'192.168.1.1',
'196.168.1.2',
],
],
'write' => [
'host' => [
'196.168.1.3',
],
],
'driver' => 'mysql',
// ... other config
],
Advanced Caching Strategies
1. Multi-Layer Caching
<?php
namespace App\Services;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Redis;
class CacheService
{
public function getPopularBlogs($limit = 10)
{
// L1: Application cache (faster)
$cacheKey = "popular_blogs_{$limit}";
return Cache::tags(['blogs', 'popular'])
->remember($cacheKey, now()->addMinutes(15), function () use ($limit) {
return Blog::select(['id', 'title', 'slug', 'excerpt', 'views'])
->published()
->orderByDesc('views')
->limit($limit)
->get();
});
}
public function warmupCache()
{
// Preload frequently accessed data
$this->getPopularBlogs();
$this->getRecentBlogs();
$this->getTrendingTags();
}
public function invalidateBlogCache($blogId)
{
Cache::tags(['blogs'])->flush();
Redis::del("blog_views_{$blogId}");
}
}
2. Redis for Session Storage
// config/session.php
'driver' => env('SESSION_DRIVER', 'redis'),
'connection' => 'session',
// config/database.php - Redis connections
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
],
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
],
'session' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_SESSION_DB', '1'),
],
],
Queue Optimization
1. High-Performance Queue Configuration
// config/queue.php
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => 90,
'block_for' => 5,
'after_commit' => false,
],
// High priority queue for critical tasks
'redis-high' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => 'high-priority',
'retry_after' => 30,
'block_for' => 1,
],
2. Efficient Job Processing
<?php
namespace App\Jobs;
use App\Models\Blog;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Queue\ShouldBeUnique;
class ProcessBlogAnalytics implements ShouldQueue, ShouldBeUnique
{
use InteractsWithQueue, Queueable, SerializesModels;
public $tries = 3;
public $maxExceptions = 1;
public $timeout = 120;
protected $blogId;
public function __construct($blogId)
{
$this->blogId = $blogId;
$this->onQueue('analytics');
}
public function handle()
{
$blog = Blog::find($this->blogId);
if (!$blog) {
$this->fail('Blog not found');
return;
}
// Process analytics in chunks to avoid memory issues
$this->processViewsData($blog);
$this->processEngagementMetrics($blog);
}
public function uniqueId()
{
return $this->blogId;
}
}
Response Time Optimization
1. HTTP Caching Headers
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class SetCacheHeaders
{
public function handle(Request $request, Closure $next, $maxAge = 3600)
{
$response = $next($request);
if ($request->isMethod('GET') && $response->status() === 200) {
$response->header('Cache-Control', "public, max-age={$maxAge}");
$response->header('Expires', gmdate('D, d M Y H:i:s', time() + $maxAge) . ' GMT');
if ($request->hasHeader('If-Modified-Since')) {
$lastModified = $this->getLastModified($request);
if ($lastModified && $request->header('If-Modified-Since') >= $lastModified) {
return response()->make('', 304);
}
}
}
return $response;
}
}
2. API Response Optimization
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class BlogResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'slug' => $this->slug,
'excerpt' => $this->excerpt,
'author' => new UserResource($this->whenLoaded('author')),
'comments_count' => $this->when(
$this->relationLoaded('comments'),
$this->comments_count
),
'published_at' => $this->published_at?->toISOString(),
'reading_time' => $this->reading_time,
];
}
public function with($request)
{
return [
'meta' => [
'cached_at' => now()->toISOString(),
'version' => 'v1',
],
];
}
}
Asset Optimization
1. Vite Configuration for Production
// vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
export default defineConfig({
plugins: [
laravel({
input: ['resources/css/app.css', 'resources/js/app.js'],
refresh: true,
}),
],
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['vue', 'axios'],
utils: ['lodash', 'moment'],
},
},
},
chunkSizeWarningLimit: 500,
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
},
},
},
});
Monitoring and Profiling
1. Laravel Telescope for Development
// Only enable in non-production environments
if (env('APP_ENV') !== 'production') {
$this->app->register(TelescopeServiceProvider::class);
}
// Custom Telescope watchers
'watchers' => [
Watchers\QueryWatcher::class => [
'enabled' => env('TELESCOPE_QUERY_WATCHER', true),
'slow' => 100, // Log queries slower than 100ms
],
Watchers\RequestWatcher::class => [
'enabled' => env('TELESCOPE_REQUEST_WATCHER', true),
'size_limit' => 64,
],
],
2. Application Performance Monitoring
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class PerformanceMonitoring
{
public function handle(Request $request, Closure $next)
{
$startTime = microtime(true);
$startMemory = memory_get_usage(true);
$response = $next($request);
$endTime = microtime(true);
$endMemory = memory_get_usage(true);
$executionTime = ($endTime - $startTime) * 1000; // Convert to milliseconds
$memoryUsage = $endMemory - $startMemory;
// Log slow requests
if ($executionTime > 1000) { // Requests slower than 1 second
Log::warning('Slow request detected', [
'url' => $request->fullUrl(),
'method' => $request->method(),
'execution_time' => $executionTime,
'memory_usage' => $memoryUsage,
'user_id' => auth()->id(),
]);
}
// Add performance headers for monitoring
$response->headers->set('X-Response-Time', number_format($executionTime, 2) . 'ms');
$response->headers->set('X-Memory-Usage', $this->formatBytes($memoryUsage));
return $response;
}
private function formatBytes($bytes, $precision = 2)
{
$units = ['B', 'KB', 'MB', 'GB'];
for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) {
$bytes /= 1024;
}
return round($bytes, $precision) . ' ' . $units[$i];
}
}
Horizontal Scaling Strategies
1. Load Balancer Configuration
# nginx.conf
upstream laravel_backend {
least_conn;
server 192.168.1.10:8000 weight=3;
server 192.168.1.11:8000 weight=3;
server 192.168.1.12:8000 weight=2;
keepalive 32;
}
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://laravel_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 5s;
proxy_send_timeout 10s;
proxy_read_timeout 10s;
}
}
2. Session Management for Multiple Servers
// config/session.php - Use database/redis for shared sessions
'driver' => env('SESSION_DRIVER', 'redis'),
'connection' => 'session',
// Ensure consistent session configuration across servers
'cookie' => env('SESSION_COOKIE', Str::slug(env('APP_NAME', 'laravel'), '_').'_session'),
'domain' => env('SESSION_DOMAIN'),
'secure' => env('SESSION_SECURE_COOKIE', true),
'same_site' => 'lax',
Advanced Performance Techniques
1. Eager Loading Optimization
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Blog extends Model
{
// Always eager load frequently accessed relationships
protected $with = ['author:id,name,avatar'];
public function scopeWithOptimalData($query)
{
return $query->select([
'id', 'title', 'slug', 'excerpt',
'author_id', 'published_at', 'views'
])
->with([
'author:id,name,avatar',
'tags:id,name,slug',
'comments' => function ($query) {
$query->approved()
->latest()
->limit(5)
->with('user:id,name');
}
])
->withCount(['comments', 'likes']);
}
// Optimize relationship queries
public function comments(): HasMany
{
return $this->hasMany(Comment::class)
->select(['id', 'blog_id', 'user_id', 'content', 'created_at'])
->approved()
->latest();
}
}
2. Database Query Caching
<?php
namespace App\Services;
class BlogService
{
public function getFeaturedBlogs($limit = 6)
{
return Cache::tags(['blogs', 'featured'])
->remember('featured_blogs', now()->addHours(2), function () use ($limit) {
return Blog::select(['id', 'title', 'slug', 'excerpt', 'featured_image'])
->featured()
->published()
->latest('published_at')
->limit($limit)
->get();
});
}
public function searchBlogs($query, $page = 1, $perPage = 15)
{
$cacheKey = "blog_search_" . md5($query . $page . $perPage);
return Cache::remember($cacheKey, now()->addMinutes(30), function () use ($query, $page, $perPage) {
return Blog::search($query)
->paginate($perPage, '*', 'page', $page);
});
}
}
Security and Performance Balance
1. Rate Limiting for API Endpoints
// routes/api.php
Route::middleware(['throttle:api'])->group(function () {
Route::get('/blogs', [BlogController::class, 'index']);
Route::middleware(['throttle:60,1'])->group(function () {
Route::post('/blogs', [BlogController::class, 'store']);
Route::put('/blogs/{blog}', [BlogController::class, 'update']);
});
});
// Custom rate limiting
RateLimiter::for('blog-creation', function (Request $request) {
return $request->user()
? Limit::perMinute(10)->by($request->user()->id)
: Limit::perMinute(2)->by($request->ip());
});
2. Input Validation Optimization
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class StoreBlogRequest extends FormRequest
{
public function rules()
{
return [
'title' => ['required', 'string', 'max:255'],
'content' => ['required', 'string', 'min:100'],
'tags' => ['array', 'max:10'],
'tags.*' => ['integer', Rule::exists('tags', 'id')],
'status' => ['required', Rule::in(['draft', 'published'])],
];
}
// Cache validation rules to avoid repeated processing
public function validationData()
{
return Cache::remember(
'validation_data_' . $this->route()->getRouteKey(),
60,
fn() => parent::validationData()
);
}
}
Deployment Optimization
1. Optimized Production Configuration
// config/app.php - Production optimizations
'debug' => env('APP_DEBUG', false),
'log_level' => env('LOG_LEVEL', 'warning'),
// config/cache.php - Use Redis for all caching
'default' => env('CACHE_DRIVER', 'redis'),
// Optimize config caching
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
2. Docker Optimization for Production
# Multi-stage build for optimized production image
FROM php:8.2-fpm-alpine AS base
RUN apk add --no-cache \
postgresql-dev \
zip \
unzip \
git \
curl
FROM base AS production
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader --no-scripts
COPY . .
RUN php artisan config:cache && \
php artisan route:cache && \
php artisan view:cache
EXPOSE 9000
CMD ["php-fpm"]
Conclusion
Scaling Laravel applications to handle millions of users requires a holistic approach combining multiple optimization strategies. The key areas to focus on are:
- Application-level optimizations: Laravel Octane, efficient queries, proper caching
- Database optimization: Proper indexing, connection pooling, read/write splitting
- Infrastructure scaling: Load balancing, horizontal scaling, CDN implementation
- Monitoring and profiling: Continuous performance monitoring and optimization
Remember that premature optimization can be counterproductive. Always profile your application to identify actual bottlenecks before implementing complex optimizations.
The techniques outlined in this guide will help you build Laravel applications that not only scale to millions of users but also maintain excellent performance and user experience.
Ready to scale your Laravel application? Start with the fundamentals and gradually implement advanced optimizations as your traffic grows.
Comments (2)