Apa itu N+1 Problem?
N+1 problem terjadi ketika kode menjalankan 1 query untuk mendapatkan daftar data, lalu N query tambahan untuk setiap item. Ini sangat tidak efisien dan menjadi lebih buruk seiring data bertambah.
Contoh N+1
// ❌ N+1 Problem
// 1 query: ambil semua users
$users = User::all();
// N queries: satu query per user untuk ambil posts
foreach ($users as $user) {
echo $user->posts->count();
// Setiap iterasi = 1 query ke database
}
// Jika ada 100 users → 101 queries!
⚠️ Skenario produksi nyata: halaman dashboard menampilkan 50 user dengan jumlah order-nya. Di local dengan 10 user terasa cepat (11 query, 30ms). Saat deploy ke production dengan 50 user per halaman, tiba-tiba halaman loading 2+ detik karena 51 query ke database. Parahnya, saat trafik naik, database overwhelmed oleh request "kecil" yang menggandakan beban. Monitor dengan Laravel Telescope atau Debugbar — kalau kamu lihat >5 query per request, kemungkinan besar ada N+1 terselubung. Di dev, enable
Model::preventLazyLoading()— Laravel akan throw exception kalau ada lazy load yang tidak di-whitelist.
Solusi: Eager Loading
// ✅ Eager Loading — hanya 2 queries
$users = User::with('posts')->get();
// Query 1: SELECT * FROM users
// Query 2: SELECT * FROM posts WHERE user_id IN (1, 2, 3, ...)
foreach ($users as $user) {
echo $user->posts->count();
// Data sudah ada di memory, tidak ada query tambahan
}
Teknik Eager Loading
// 1. with() — eager load saat query
$users = User::with('posts', 'profile')->get();
// 2. Nested eager loading
$users = User::with('posts.comments')->get();
// 3. Eager load dengan constraint
$users = User::with(['posts' => function ($query) {
$query->where('published', true)->orderBy('created_at', 'desc');
}])->get();
// 4. withCount() — hanya hitung, tidak load semua data
$users = User::withCount('posts')->get();
// $user->posts_count tersedia tanpa load semua posts
Cara Mendeteksi N+1
- Gunakan Laravel Debugbar — tampilkan jumlah query per request
- Gunakan Model::preventLazyLoading() di development
- Monitor slow query log untuk query yang berulang
// Di AppServiceProvider
public function boot(): void
{
Model::preventLazyLoading(! $this->app->isProduction());
}