Laravel Performance Optimization: Scaling to Million Users in 2025

Salman Hassan
May 30, 2025
10 min read
2 comments

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:

  1. Application-level optimizations: Laravel Octane, efficient queries, proper caching
  2. Database optimization: Proper indexing, connection pooling, read/write splitting
  3. Infrastructure scaling: Load balancing, horizontal scaling, CDN implementation
  4. 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)

Leave a Comment
D
David Park
6 months ago

Incredible depth on Laravel performance optimization. The Octane configuration and database optimization sections are particularly well-explained.

Reply to David Park
L
Lisa Wang
6 months ago

This is a comprehensive guide to scaling Laravel apps. The monitoring middleware example is something I'm definitely implementing in our production app.

Reply to Lisa Wang