Pernah nggak kamu nulis kode Laravel yang kelihatannya bersih dan rapi, tapi begitu dicek di log, ternyata ada ratusan query SQL yang jalan hanya untuk menampilkan daftar 20 artikel? Kalau pernah, selamat — kamu baru ketemu yang namanya N+1 Query Problem. Ini adalah salah satu masalah performa paling umum yang dialami developer Laravel pemula, dan kabar baiknya: solusinya simpel banget begitu kamu paham konsepnya.
Di artikel ke-16 dari seri 50 Artikel Belajar Laravel ini, kita akan bedah tuntas perbedaan Eager Loading dan Lazy Loading di Eloquent — lengkap dengan contoh nyata, perbandingan query, dan kapan kamu harus pakai yang mana. Sama seperti saat kamu menggunakan migration Laravel untuk menyusun struktur database dengan rapi, memilih loading strategy yang tepat adalah pondasi performa aplikasimu.
N+1 Query Problem terjadi ketika kamu menjalankan 1 query utama untuk mengambil data, lalu menjalankan N query tambahan (satu per record) untuk mengambil data relasi — menghasilkan N+1 total query yang membebani database.
🍕 Apa Itu N+1 Problem? Analogi Pizza yang Bikin Paham
Bayangkan kamu adalah pelayan di restoran pizza. Ada 10 meja tamu. Bos kamu bilang: "Tanyain tiap meja, mereka mau topping apa."
Cara bodoh: Kamu bolak-balik ke dapur 10 kali — sekali per meja — untuk catat pesanan topping. Total perjalanan: 10 kali. Capek, lambat, nggak efisien.
Cara cerdas: Kamu bawa satu notepad, tanyain semua 10 meja sekaligus, baru ke dapur 1 kali. Beres!
Itulah perbedaan Lazy Loading (cara bodoh) vs Eager Loading (cara cerdas) di Laravel.
Menurut Laravel Debugbar, 70% aplikasi Laravel pemula memiliki N+1 problem yang tidak disadari. Ini bisa menyebabkan halaman yang harusnya load 0.2 detik jadi 3–5 detik hanya karena query tidak efisien.
Untuk memahami ini secara teknis, kita perlu punya database yang sudah disiapkan. Di sinilah migration Laravel berperan — migration memastikan tabel posts dan users punya struktur yang benar sebelum kita bisa bermain-main dengan relasi Eloquent.
🛠 Setup Migration Laravel dan Model Relasi
Sebelum masuk ke Eager/Lazy Loading, kita siapkan dulu database-nya menggunakan migration Laravel. Kita akan buat tabel users dan posts dengan relasi one-to-many.
Buat Migration untuk Tabel Posts
Jalankan perintah Artisan berikut di terminal:
php artisan make:migration create_posts_table --create=posts php artisan make:migration create_comments_table --create=comments
Isi File Migration Posts
Buka file migration yang baru dibuat di folder database/migrations/ dan edit seperti ini:
public function up(): void { Schema::create('posts', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained()->onDelete('cascade'); $table->string('title'); $table->text('body'); $table->timestamps(); }); }
Jalankan Migration
Setelah migration siap, jalankan perintah berikut untuk membuat tabel di database:
php artisan migrate
Gunakan foreignId('user_id')->constrained() agar Laravel otomatis membuat foreign key constraint ke tabel users. Ini jauh lebih clean dibanding cara lama menggunakan $table->unsignedBigInteger() manual.
Definisikan Relasi di Model
Buka model User.php dan Post.php, tambahkan relasi:
// User punya banyak Post public function posts() { return $this->hasMany(Post::class); } // Di Post.php — Post dimiliki oleh satu User public function user() { return $this->belongsTo(User::class); }
⚔️ Lazy Loading vs Eager Loading: Bedanya di Mana?
Setelah migration Laravel dan model kita siap, sekarang saatnya melihat perbedaan nyata antara dua pendekatan loading ini dengan kode konkret.
❌ Cara SALAH — Lazy Loading (N+1 Problem):
// Ini menghasilkan 1 query untuk posts // + 1 query per post untuk mengambil user-nya = N+1 ! $posts = Post::all(); foreach ($posts as $post) { // Setiap iterasi trigger 1 query baru ke database! echo $post->user->name; } // Jika ada 50 post → 51 query dijalankan 😱
✅ Cara BENAR — Eager Loading dengan with():
// Hanya 2 query: 1 untuk posts, 1 untuk semua users $posts = Post::with('user')->get(); foreach ($posts as $post) { // Data user sudah di-cache di memori, nggak ada query baru! echo $post->user->name; } // Jika ada 50 post → tetap hanya 2 query ✅ // Load beberapa relasi sekaligus: $posts = Post::with(['user', 'comments', 'tags'])->get(); // Nested eager loading: $posts = Post::with('comments.user')->get();
Eloquent with() bekerja dengan menjalankan query kedua menggunakan klausa WHERE IN. Jadi alih-alih 50 query individual, ia mengirim satu query seperti: SELECT * FROM users WHERE id IN (1, 2, 3, ...) — jauh lebih efisien!
📊 Perbandingan Lengkap: Eager vs Lazy vs Lazy Eager
Laravel sebenarnya punya 3 jenis loading. Berikut tabel perbandingan lengkapnya:
| Metode | Sintaks | Jumlah Query | Kapan Dipakai |
|---|---|---|---|
| Lazy Loading | $post->user |
N+1 ❌ | Hanya 1 record tunggal |
| Eager Loading | Post::with('user') |
2 Query ✅ | Koleksi / list data |
| Lazy Eager Loading | $posts->load('user') |
2 Query ✅ | Relasi kondisional/dinamis |
| Eager (Kondisional) | withCount(), withSum() |
2 Query ✅ | Agregat/statistik relasi |
Lazy Eager Loading (atau deferred loading) adalah cara tengah: kamu ambil data dulu, baru load relasi belakangan menggunakan load().
$posts = Post::all(); // Query pertama // Lakukan sesuatu dulu... if ($needsUser) { $posts->load('user'); // Query kedua, hanya kalau dibutuhkan }
Cocok ketika kamu perlu memutuskan secara dinamis apakah relasi perlu di-load atau tidak.
Gunakan Laravel Telescope atau Debugbar untuk mendeteksi N+1 problem di aplikasimu. Jalankan composer require barryvdh/laravel-debugbar --dev lalu pantau tab "Queries" saat browsing halaman.
🚀 Teknik Lanjutan: withCount, withSum, dan Eager Loading Kondisional
Selain with() biasa, Laravel punya helper yang sangat berguna untuk mengambil agregat relasi tanpa N+1 problem:
// Ambil jumlah komentar tiap post (tanpa N+1!) $posts = Post::withCount('comments')->get(); // Di blade: {{ $post->comments_count }} // Eager loading dengan kondisi (constrained eager loading) $posts = Post::with(['comments' => function ($query) { $query->where('approved', true) ->latest() ->limit(3); }])->get(); // withSum - total nilai dari kolom relasi $users = User::withSum('orders', 'total_price')->get(); // Di blade: {{ $user->orders_sum_total_price }}
Kamu bisa mendefinisikan relasi yang selalu di-eager load langsung di model menggunakan property $with:protected $with = ['user', 'category'];
Tapi hati-hati — ini akan selalu load relasi tersebut di setiap query, bahkan saat tidak dibutuhkan. Gunakan hanya untuk relasi yang memang selalu diperlukan.
Kamu Sekarang Punya Senjata Ampuh Melawan Query Lambat!
N+1 Problem adalah akar masalah performa di banyak aplikasi Laravel pemula — 1 query utama + N query relasi per record.
Eager Loading dengan with() adalah solusi utama — selalu gunakan ini saat menampilkan koleksi data dengan relasi.
Migration Laravel yang baik memastikan struktur relasi tabel kamu benar sebelum Eloquent bisa bekerja optimal.
Gunakan withCount(), withSum() untuk agregat, dan load() untuk loading kondisional.
Pasang Laravel Debugbar di lingkungan development untuk selalu memantau jumlah query yang berjalan.
Ini adalah artikel ke-16 dari seri 50 Artikel Belajar Laravel. Kalau artikel ini membantumu, bantu sebarkan ke teman-teman yang juga lagi belajar Laravel. Drop pertanyaan di kolom komentar — saya baca semua komentar! 🙌
TAG TOPIK:
No comments:
Post a Comment