Why Eloquent consumes 10x more memory than the Query Builder.
Eloquent is expensive. Every row from the database is "hydrated" into a full Model object with:
The Query Builder returns simple stdClass objects or arrays. No overhead. Pure data.
$users = User::all();
// Returns 100,000 Model objects
Memory: 500-800 MB
Time: 3-5 seconds
Result: 💀 SERVER CRASH (Out of Memory)
$users = DB::table('users')->get();
// Returns 100,000 stdClass objects
Memory: 50-80 MB
Time: 0.5-1 second
Result: ✅ Server survives
| Type | Memory per Row | Total for 10k Rows | Overhead |
|---|---|---|---|
| Eloquent Model (full) | ~5-10 KB | 50-100 MB | 10x |
| stdClass (Query Builder) | ~0.5-1 KB | 5-10 MB | 1x (baseline) |
| Array (DB::select) | ~0.3-0.5 KB | 3-5 MB | 0.5x (smallest) |
Eloquent uses 5-10x more memory than the Query Builder. For read-only operations, reports, exports, or APIs, using Eloquent can crash your server unnecessarily.
// User.php
class User extends Model {
protected $casts = [
'email_verified_at' => 'datetime',
'preferences' => 'array',
'is_active' => 'boolean',
];
public function posts() {
return $this->hasMany(Post::class);
}
}
// Controller
$users = User::where('is_active', true)
->select('id', 'name', 'email')
->get();
// Each user is a FULL Model object with:
// - Casts (datetime, array, boolean)
// - Event dispatcher
// - Original values array
// - Relationship loader
// - Dirty checking
// No Model class needed
// Controller
$users = DB::table('users')
->where('is_active', true)
->select('id', 'name', 'email')
->get();
// Each user is a simple stdClass object:
// stdClass {
// id → 1
// name → "John"
// email → "john@example.com"
// }
//
// That's it. No casts. No events. No relations.
// Just pure data.
// Even lighter than stdClass
$users = DB::table('users')
->where('is_active', true)
->select('id', 'name', 'email')
->get()
->toArray(); // Convert to plain array
// Or directly:
$users = DB::select('SELECT id, name, email FROM users WHERE is_active = ?', [true]);
// Returns array of arrays: [['id'=>1, 'name'=>'John', ...], ...]
Arrays are the absolute cheapest data structure in PHP. Use them for exports and reports.
$users = User::all(); // 50,000 models
foreach ($users as $user) {
fputcsv($file, [$user->id, $user->name, $user->email]);
}
// Memory: ~400 MB
// Time: ~10 seconds
// Risk: OOM on small servers
DB::table('users')->chunk(1000, function ($users) use ($file) {
foreach ($users as $user) {
fputcsv($file, [$user->id, $user->name, $user->email]);
}
});
// Memory: ~10 MB
// Time: ~2 seconds
// Safe on any server
// ProductController.php
return Product::all()->toJson();
// Response size: ~5 MB JSON
// Memory usage: ~100 MB
// Response time: ~500 ms
// ProductController.php
return response()->json(
DB::table('products')->get()
);
// Response size: ~5 MB JSON
// Memory usage: ~15 MB
// Response time: ~200 ms
$orders = Order::with('user')
->latest()
->limit(50)
->get();
// 50 models is fine
// Memory: ~2 MB
// Use Eloquent for relationships
$orders = DB::table('orders')
->join('users', 'orders.user_id', '=', 'users.id')
->select('orders.*', 'users.name')
->latest('orders.created_at')
->limit(50)
->get();
// Either works for 50 rows
For reports with MILLIONS of rows, even the Query Builder's stdClass creation has overhead. Go straight to PDO.
$pdo = DB::connection()->getPdo();
$stmt = $pdo->prepare("
SELECT id, name, email, created_at
FROM users
WHERE created_at > ?
");
$stmt->execute(['2024-01-01']);
// Fetch as associative array (cheapest)
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
// Process row
}
// No models. No stdClass. No overhead.
// Just PHP arrays directly from the database driver.
| Eloquent | CRASH (OOM) |
| Query Builder | ~500 MB |
| Raw PDO (streaming) | ~2 MB (constant) |
// ReportController.php - Export 5 million users
public function exportLargeReport()
{
$pdo = DB::connection()->getPdo();
$stmt = $pdo->prepare("SELECT id, name, email FROM users");
$stmt->execute();
$filename = storage_path('exports/users.csv');
$file = fopen($filename, 'w');
fputcsv($file, ['ID', 'Name', 'Email']);
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
fputcsv($file, $row);
// Memory stays constant. No accumulation.
}
fclose($file);
return response()->download($filename);
}
This processes 5 million rows with ~5MB of RAM. Eloquent would need 40GB+ and crash instantly.
| Method | Memory per Row | 10k Rows Memory | When to Use |
|---|---|---|---|
| Eloquent Model | ~5-10 KB | 50-100 MB | CRUD, relationships, events, casts, small datasets |
| Query Builder (stdClass) | ~0.5-1 KB | 5-10 MB | Read-only, large datasets, APIs |
| DB::select() (array) | ~0.3-0.5 KB | 3-5 MB | Reports, exports, raw SQL |
| Raw PDO (streaming) | ~0 bytes (streamed) | ~5 MB (constant) | Millions of rows, exports, ETL |
Topic 6: Queue Serialization Trap — Why sending full models to queues kills your Redis memory. The ID-only pattern that saves 99% of queue payload size.