Aplikasi Laravel yang lambat hampir selalu disebabkan oleh query database yang tidak efisien, bukan bahasa PHP-nya. Kabar baiknya, Eloquent menyediakan banyak alat untuk membuat query cepat — asalkan Anda tahu cara memakainya. Artikel ini membahas teknik optimasi query Eloquent yang paling berdampak, dari yang paling umum (N+1) hingga indexing dan caching, lengkap dengan kode nyata.
1. Atasi Masalah N+1 dengan Eager Loading
Ini adalah penyebab lambat nomor satu. N+1 terjadi ketika Anda mengambil sekumpulan data lalu mengakses relasinya di dalam loop:
$orders = Order::all();
foreach ($orders as $order) {
echo $order->customer->name; // 1 query TAMBAHAN tiap iterasi
}
Untuk 500 order, itu berarti 501 query. Gunakan with() untuk memuat relasi sekaligus:
$orders = Order::with('customer')->get(); // hanya 2 query
Anda juga bisa memuat relasi bersarang dan membatasi kolomnya:
$orders = Order::with([
'customer:id,name',
'items.product:id,name,price',
])->get();
Agar N+1 tidak lolos ke produksi, aktifkan pencegahan otomatis di AppServiceProvider:
use Illuminate\Database\Eloquent\Model;
public function boot(): void
{
Model::preventLazyLoading(! app()->isProduction());
}
2. Ambil Hanya Kolom yang Dibutuhkan dengan select()
Secara default Eloquent menjalankan SELECT *. Jika tabel punya kolom berat seperti content atau metadata, Anda memboroskan memori dan bandwidth. Ambil kolom seperlunya:
$users = User::select('id', 'name', 'email')->get();
Jika hanya butuh satu kolom sebagai array, gunakan pluck() yang jauh lebih hemat:
$emails = User::where('active', true)->pluck('email');
3. Proses Data Besar dengan chunk() dan lazy()
Memuat 100.000 baris dengan get() bisa membuat aplikasi kehabisan memori. Untuk memproses data besar, ambil per potongan:
User::chunk(500, function ($users) {
foreach ($users as $user) {
// proses tiap user
}
});
Alternatif yang lebih modern adalah lazy(), yang mengembalikan LazyCollection sehingga Anda tetap bisa memakai foreach biasa tanpa membebani memori:
foreach (User::lazy() as $user) {
// hanya sebagian kecil baris di memori setiap saat
}
Untuk update massal, jika Anda mengubah kolom yang dipakai sebagai syarat, gunakan chunkById() agar tidak melewati baris.
4. Tambahkan Index Database
Index adalah cara paling ampuh mempercepat query WHERE, ORDER BY, dan JOIN. Kolom yang sering difilter (misalnya user_id, status, atau email) sebaiknya diberi index lewat migration:
Schema::table('orders', function (Blueprint $table) {
$table->index('status');
$table->index(['user_id', 'created_at']); // composite index
});
Foreign key sebaiknya selalu ter-index. Tanpa index, database harus memindai seluruh tabel (full table scan) untuk setiap query — sangat lambat saat data membesar.
5. Cache Hasil Query yang Jarang Berubah
Data seperti daftar kategori atau pengaturan situs jarang berubah tetapi sering diambil. Simpan hasilnya di cache agar tidak menembak database berulang kali:
use Illuminate\Support\Facades\Cache;
$categories = Cache::remember('categories', 3600, function () {
return Category::orderBy('name')->get();
});
remember() mengembalikan data dari cache jika ada; jika tidak, ia menjalankan query, menyimpannya selama 3600 detik, lalu mengembalikannya. Jangan lupa hapus cache saat data diperbarui dengan Cache::forget('categories').
6. Temukan Query yang Lambat
Anda tidak bisa mengoptimasi apa yang tidak Anda ukur. Cara cepat memantau semua query adalah dengan DB::listen di AppServiceProvider:
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
DB::listen(function ($query) {
if ($query->time > 100) { // lebih dari 100 ms
Log::warning('Query lambat', [
'sql' => $query->sql,
'time' => $query->time,
]);
}
});
Untuk pengembangan lokal, pasang Laravel Debugbar yang menampilkan jumlah query, durasi, dan duplikat langsung di browser:
composer require barryvdh/laravel-debugbar --dev
Debugbar akan langsung menyorot masalah N+1 karena Anda melihat query yang sama diulang berkali-kali.
7. Hitung dengan Benar
Untuk menghitung jumlah baris, jangan pernah memakai count(User::all()) yang memuat semua data ke memori. Gunakan agregat database:
$total = Order::where('status', 'paid')->count();
Untuk mengecek keberadaan data, exists() lebih cepat daripada count() > 0:
if (Order::where('user_id', $id)->exists()) {
// ...
}
8. Pindahkan Perhitungan Berat ke Database
Kesalahan umum lain adalah mengambil banyak baris ke PHP hanya untuk menjumlahkan atau merata-ratakan nilainya. Serahkan pekerjaan itu ke database yang jauh lebih efisien untuk agregasi:
// Kurang efisien: memuat semua baris ke memori
$total = Order::where('status', 'paid')->get()->sum('amount');
// Efisien: database yang menghitung
$total = Order::where('status', 'paid')->sum('amount');
Hal yang sama berlaku untuk avg(), max(), dan min(). Untuk statistik yang mengelompokkan data, gunakan groupBy() di level query agar hanya hasil ringkasnya yang dikirim ke PHP. Ini bisa mengubah query yang memuat ribuan baris menjadi satu baris hasil saja.
Jika Anda menghitung jumlah relasi, hindari memuat seluruh relasi hanya untuk menghitungnya. Gunakan withCount():
$posts = Post::withCount('comments')->get();
// akses via $post->comments_count tanpa memuat komentarnya
Kesimpulan
Optimasi query Eloquent bukan sihir — ini tentang mengukur lalu memperbaiki. Prioritas Anda: hilangkan N+1 dengan eager loading, ambil hanya kolom yang perlu, index kolom yang sering difilter, proses data besar dengan chunk/lazy, cache data yang jarang berubah, dan serahkan agregasi ke database. Dengan Debugbar atau DB::listen sebagai mata Anda, aplikasi Laravel yang tadinya berat bisa menjadi ringan dan responsif. Terapkan satu per satu, ukur dampaknya, dan Anda akan melihat waktu respons turun drastis.
Tools untuk Memantau Query
Optimasi yang baik dimulai dari pengukuran, bukan tebakan. Manfaatkan alat pemantau seperti Laravel Debugbar atau Telescope untuk melihat jumlah kueri yang dijalankan tiap halaman, durasi eksekusinya, dan mendeteksi masalah N plus satu. Aktifkan query log saat pengembangan untuk menelusuri kueri yang lambat, lalu gunakan penjelasan rencana eksekusi database untuk memahami mengapa sebuah kueri lambat. Dengan data nyata di tangan, Anda dapat menentukan indeks yang tepat, memilih eager loading yang sesuai, dan menghindari optimasi prematur pada bagian yang sebenarnya tidak menjadi hambatan. Pemantauan rutin juga membantu menangkap regresi performa sejak dini sebelum berdampak ke pengguna. Ingat, kueri tercepat adalah kueri yang tidak perlu dijalankan, jadi pertimbangkan pula caching untuk data yang jarang berubah.