📁 Volume V: The 10 Disasters

💣 Topic 15: The 10 Disasters

The most common production failures. Even senior developers make these mistakes.

"Experience is what you get when you didn't get what you wanted.
Learn from these disasters. Your production server will thank you."
⚠️ REALITY CHECK

Every single one of these disasters has happened in production to real Laravel applications. Some caused hours of downtime. Some cost companies thousands of dollars. Read them. Memorize them. Prevent them.

1
DB::enableQueryLog() in Production
💀 CRITICAL
🔴 THE PROBLEM:

DB::enableQueryLog() stores EVERY query in memory. After a few hours of production traffic, memory consumption grows until the server crashes with "Out of Memory".

// 💀 NEVER DO THIS IN PRODUCTION
DB::enableQueryLog();
$users = User::all();
$queries = DB::getQueryLog();  // This stays in memory forever!
✅ THE SOLUTION:

Never use DB::enableQueryLog() in production. Use Laravel Debugbar in development, or Telescope for production monitoring.

// Only in local environment
if (app()->environment('local')) {
    DB::enableQueryLog();
}
2
env() After config:cache
💀 CRITICAL
🔴 THE PROBLEM:

After running php artisan config:cache, the .env file is no longer loaded. Any call to env() outside config/*.php returns null. Database connections fail. The app crashes.

// 💀 In a controller or model - CRASHES after config:cache
$apiKey = env('STRIPE_KEY');

// 💀 In migration - CRASHES
$database = env('DB_DATABASE');
✅ THE SOLUTION:

Never use env() outside config/*.php. Use config() everywhere else.

// ✅ CORRECT - Works before AND after cache
$apiKey = config('services.stripe.key');
3
User::all() for Large Datasets
💀 CRITICAL
🔴 THE PROBLEM:

User::all() loads ALL rows into memory. With 50,000 users, that's ~500MB of RAM. With 500,000 users, the server crashes with Out of Memory.

// 💀 Exporting 100k users - CRASHES
$users = User::all();  // 500MB+ memory
foreach ($users as $user) {
    // process
}
✅ THE SOLUTION:

Use lazy(), chunk(), or cursor() to stream data.

// ✅ Memory stays constant (~5MB)
foreach (User::cursor() as $user) {
    // process one user at a time
}
4
Full Model in Queue
💀 CRITICAL
🔴 THE PROBLEM:

Dispatching a full Model serializes ALL attributes (and relations!). Payload size can be 100KB-10MB per job. 10,000 jobs = 1GB+ in Redis. Queue stops. Server memory exhausted.

// 💀 DO NOT DO THIS
ProcessUserJob::dispatch($user);  // Serializes entire user + relations!
✅ THE SOLUTION:

Dispatch only the ID. Fetch fresh data inside the job.

// ✅ Dispatch ID only (8 bytes)
ProcessUserJob::dispatch($user->id);

// Inside job
$user = User::find($this->userId);
5
Forgotten php artisan optimize
⚠️ HIGH
🔴 THE PROBLEM:

Without optimization, Laravel reads all config files, parses all routes, and discovers all events on EVERY request. 30-50ms of wasted CPU per request.

// 💀 Missing from deployment script
# Deploy without optimize
git pull
composer install
php artisan migrate
✅ THE SOLUTION:

Always run php artisan optimize after deployment.

# Deployment script
git pull
composer install --no-dev --optimize-autoloader
php artisan migrate --force
php artisan optimize  # ← CRITICAL!
6
Lazy Loading N+1 in Views
⚠️ HIGH
🔴 THE PROBLEM:

You eager loaded posts, but forgot to eager load profile or comments. The view triggers N+1 queries silently. Works in development (small data), kills production (10,000+ rows).

// 💀 Controller
$users = User::with('posts')->get();

// 💀 View - N+1 for 'profile' relation
@foreach ($users as $user)
    {{ $user->profile->bio }}  // Extra query per user!
@endforeach
✅ THE SOLUTION:

Enable preventLazyLoading() in development to catch these bugs.

// AppServiceProvider.php
Model::preventLazyLoading(!$this->app->isProduction());

// Now Laravel throws an exception if you forget eager loading!
7
Log::info() Inside Loops
⚠️ HIGH
🔴 THE PROBLEM:

Each Log::info() opens a file, writes, and closes it. With 10,000 iterations, that's 10,000 file I/O operations. Server I/O becomes the bottleneck.

// 💀 10,000 file writes
foreach ($users as $user) {
    Log::info("Processing user {$user->id}");
    $user->process();
}
✅ THE SOLUTION:

Batch your logs or use memory logging.

// ✅ One file write
$logs = [];
foreach ($users as $user) {
    $logs[] = "Processing user {$user->id}";
    $user->process();
}
Log::info(implode("\n", $logs));
8
File Session Driver
⚠️ HIGH
🔴 THE PROBLEM:

File-based sessions use file locking. When 100 concurrent users request the same session file, they wait in line. Response times skyrocket. Server crashes.

# .env - DEFAULT (DANGEROUS for production)
SESSION_DRIVER=file  # ← File locking hell!
✅ THE SOLUTION:

Use Redis for sessions (and cache).

# .env - PRODUCTION READY
SESSION_DRIVER=redis
CACHE_DRIVER=redis
9
Missing Index on WHERE Columns
⚠️ HIGH
🔴 THE PROBLEM:

A WHERE email = '...' clause without an index causes a full table scan. With 1M users, the query takes 5 seconds instead of 0.001 seconds.

-- 💀 No index = Full table scan (5 seconds)
SELECT * FROM users WHERE email = 'user@example.com';
✅ THE SOLUTION:

Add indexes to all WHERE, JOIN, and ORDER BY columns. Run EXPLAIN to verify.

-- ✅ With index (0.001 seconds)
CREATE INDEX idx_users_email ON users(email);

-- Verify with EXPLAIN
EXPLAIN SELECT * FROM users WHERE email = 'user@example.com';
-- type should be 'ref' or 'range', NOT 'ALL'
10
pm.max_requests = 0 in PHP-FPM
💀 CRITICAL
🔴 THE PROBLEM:

Default setting pm.max_requests = 0 means PHP processes live forever. Memory leaks accumulate over time. After 3-7 days, each process consumes 500MB+ RAM. Server crashes. Restart fixes temporarily, then repeats.

# php-fpm.conf - DEFAULT (DANGEROUS)
pm.max_requests = 0  # ← Memory leak accumulator!
✅ THE SOLUTION:

Set pm.max_requests to 500-1000. Processes restart after handling that many requests, freeing any leaked memory.

# php-fpm.conf - PRODUCTION READY
pm.max_requests = 500
pm.max_children = (Total RAM - Other services) / 30MB

📋 The Complete Pre-Deployment Checklist

✅ Verify These Before Every Production Deployment

📌 THE RULE: Before every deployment, run through this checklist. One missing item can take down your production server. Don't learn these lessons the hard way.
NEXT TOPIC PREVIEW

Topic 16: PHP-FPM Deep Dive — Understanding pm.max_children, pm.start_servers, pm.min_spare_servers, pm.max_spare_servers, pm.max_requests, and how to calculate the right values for your server.