Memuat...
👋 Selamat Pagi!

Kesalahan Umum Struktur Database Laravel yang Bikin Aplikasi Lambat

Performa Laravel bukan cuma soal query — schema database yang salah bisa bikin aplikasi lambat tanpa kamu sadari. Pelajari cara memperbaikinya di sini.

Kesalahan Umum Struktur Database Laravel yang Bikin Aplikasi Lambat

Banyak developer Laravel langsung menuduh query sebagai biang keladi ketika aplikasi mulai melambat.

Padahal, akar masalahnya sering jauh lebih dalam — tersembunyi di desain schema yang salah sejak awal.

Artikel ini membongkar kesalahan struktur database yang paling sering dilakukan developer Laravel Indonesia, lengkap dengan cara mengidentifikasi dan memperbaikinya sebelum terlambat.

Masalah N+1 Query: Mengapa Eloquent Relationship Bisa Jadi Bumerang

Ini adalah kesalahan paling klasik sekaligus paling mahal di Laravel.

Bayangkan kamu menampilkan daftar 100 pesanan beserta nama pelanggannya masing-masing.

// Kode yang terlihat bersih, tapi mematikan
$orders = Order::all();

foreach ($orders as $order) {
    echo $order->user->name; // Query baru dieksekusi di setiap iterasi!
}

Kode di atas mengeksekusi 101 query — 1 untuk mengambil semua pesanan, lalu 1 lagi untuk setiap pesanan demi mendapatkan data user-nya.

Solusinya adalah eager loading dengan with().

// Hanya 2 query, berapapun jumlah pesanannya
$orders = Order::with('user')->get();

foreach ($orders as $order) {
    echo $order->user->name; // Data sudah di-load sebelumnya
}

Perbedaannya bisa sangat dramatis — dari ratusan milidetik menjadi puluhan milidetik.

Nested Relationship yang Lebih Berbahaya

Masalah N+1 menjadi jauh lebih serius ketika kamu berurusan dengan relasi berjenjang.

// N+1 berlapis — bencana performa
$categories = Category::all();

foreach ($categories as $category) {
    foreach ($category->products as $product) {
        echo $product->reviews->count(); // Query meledak di sini
    }
}

Gunakan dot notation untuk eager load relasi nested sekaligus.

$categories = Category::with('products.reviews')->get();

Satu hal yang sering terlewat: aktifkan preventLazyLoading() di AppServiceProvider saat development agar Laravel langsung melempar exception ketika ada lazy loading yang tidak disengaja.

// AppServiceProvider.php
public function boot()
{
    Model::preventLazyLoading(! app()->isProduction());
}

Kesalahan Indeks Database yang Paling Sering Diabaikan Developer Junior

Indeks adalah fitur database yang paling sering disalahpahami.

Banyak developer menambahkan indeks di mana-mana dengan harapan semua query menjadi cepat — ini keliru.

Ada juga yang tidak menambahkan indeks sama sekali karena takut "membebani" database — ini juga keliru.

Kolom yang Wajib Diindeks

Aturan sederhananya: indeks kolom yang sering muncul di klausa WHERE, ORDER BY, dan JOIN.

// Migration dengan indeks yang tepat
Schema::create('orders', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('user_id');
    $table->string('status'); // Sering di-filter
    $table->timestamp('created_at');
    
    // Indeks untuk foreign key
    $table->index('user_id');
    
    // Indeks komposit untuk query yang sering muncul bersamaan
    $table->index(['status', 'created_at']);
    
    // Foreign key otomatis menambah indeks di Laravel 8+
    $table->foreign('user_id')->references('id')->on('users');
});

Composite Index: Urutan Kolom Sangat Penting

Ini yang sering salah kaprah — urutan kolom dalam composite index bukan sekadar formalitas.

Database hanya bisa menggunakan composite index jika query dimulai dari kolom pertama dalam index tersebut.

// Index: ['status', 'created_at']
// Query ini MEMANFAATKAN index
Order::where('status', 'pending')
     ->orderBy('created_at')
     ->get();

// Query ini TIDAK memanfaatkan index sepenuhnya
Order::where('created_at', '>', now()->subDay())
     ->get(); // Kolom pertama (status) tidak ada

Prinsipnya: urutkan dari kolom dengan selektivitas tertinggi (nilai paling beragam) ke terendah.

Kesalahan Index yang Memperlambat Write

Setiap indeks yang kamu tambahkan memperlambat operasi INSERT, UPDATE, dan DELETE karena database harus memperbarui semua indeks terkait.

Audit tabel yang memiliki lebih dari 5-6 indeks — kemungkinan besar ada indeks yang tumpang tindih atau tidak pernah digunakan.

Kapan Harus Denormalisasi Tabel demi Performa Read yang Lebih Cepat

Normalisasi database itu penting, tapi bukan hukum yang tidak boleh dilanggar.

Ketika aplikasi bersifat read-heavy (lebih banyak baca daripada tulis), denormalisasi tertentu justru menjadi keputusan arsitektur yang tepat.

Contoh Kasus: Counter Cache

Bayangkan setiap kali kamu ingin menampilkan jumlah komentar per artikel, kamu harus menjalankan query COUNT(*).

// Setiap page load menjalankan COUNT query yang mahal
$articles = Article::withCount('comments')->get();

Dengan denormalisasi menggunakan counter cache, kamu menyimpan hitungan langsung di tabel articles.

// Migration
$table->unsignedInteger('comments_count')->default(0);

// Observer untuk menjaga data tetap sinkron
class CommentObserver
{
    public function created(Comment $comment): void
    {
        $comment->article->increment('comments_count');
    }

    public function deleted(Comment $comment): void
    {
        $comment->article->decrement('comments_count');
    }
}

Sekarang query untuk menampilkan jumlah komentar hanya membaca satu kolom integer — jauh lebih cepat.

Menyimpan Data Agregat untuk Laporan

Tabel laporan atau dashboard sering membutuhkan agregasi besar yang tidak efisien jika dihitung real-time.

Pertimbangkan untuk menyimpan data ringkasan di tabel terpisah yang diperbarui secara berkala menggunakan Laravel Scheduler.

// Tabel daily_sales_summary menyimpan data yang sudah diaregasi
// Lebih cepat dari: SELECT SUM(amount) FROM orders WHERE DATE(created_at) = ?

Kuncinya: selalu buat mekanisme sinkronisasi yang andal agar data denormalisasi tidak basi.

Menggunakan Laravel Telescope dan Query Log untuk Audit Performa

Kamu tidak bisa memperbaiki apa yang tidak kamu ukur.

Laravel menyediakan dua alat utama untuk mengidentifikasi query bermasalah sebelum masuk production.

Laravel Telescope

Telescope adalah debugbar on-steroid untuk aplikasi Laravel.

Install terlebih dahulu via Composer.

composer require laravel/telescope --dev
php artisan telescope:install
php artisan migrate

Setelah aktif, buka /telescope di browser dan klik tab Queries.

Telescope akan menampilkan setiap query yang berjalan, berapa lama durasinya, dan — yang paling berharga — dari baris kode mana query tersebut dipicu.

Perhatikan query yang muncul berulang kali dengan pola yang sama — itu hampir pasti N+1.

DB::listen() untuk Profiling Manual

Untuk analisis yang lebih granular, kamu bisa mendaftarkan listener langsung di kode.

// Taruh di AppServiceProvider atau route closure saat debugging
DB::listen(function ($query) {
    if ($query->time > 100) { // Query lebih dari 100ms
        Log::warning('Slow query detected', [
            'sql'      => $query->sql,
            'bindings' => $query->bindings,
            'time'     => $query->time . 'ms',
        ]);
    }
});

Menganalisis Query dengan EXPLAIN

Saat menemukan query yang mencurigakan, gunakan EXPLAIN untuk melihat bagaimana MySQL menjalankannya.

$results = DB::select('EXPLAIN ' . $sql);
dd($results);

Perhatikan kolom type dalam hasil EXPLAIN.

  • const atau eq_ref — sangat efisien
  • ref — baik, menggunakan indeks
  • range — cukup baik untuk query range
  • ALLbahaya, full table scan, indeks tidak digunakan

Jika kamu melihat ALL pada tabel yang besar, itu sinyal bahwa indeks perlu ditambahkan atau query perlu direfaktor.

Kesulitan dengan tugas programming atau butuh bantuan coding? KerjaKode siap membantu menyelesaikan tugas IT dan teknik informatika Anda. Dapatkan bantuan profesional di jasa tugas IT KerjaKode.

Strategi Caching dengan Redis untuk Query yang Tidak Bisa Dioptimalkan Lagi

Ada query yang secara struktural memang lambat — bukan karena kesalahan coding, tapi karena kompleksitas datanya memang tinggi.

Di sinilah caching menjadi senjata terakhir.

Setup Redis di Laravel

Pastikan extension Redis sudah terpasang dan konfigurasi .env sudah benar.

CACHE_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379

Cache Query Result dengan Facade Cache

Pola dasar caching query di Laravel sangat sederhana.

use Illuminate\Support\Facades\Cache;

$popularProducts = Cache::remember('popular_products', now()->addHours(1), function () {
    return Product::with('category')
        ->where('is_active', true)
        ->orderByDesc('sales_count')
        ->limit(20)
        ->get();
});

remember() bekerja seperti ini: cek cache dulu, kalau ada kembalikan dari cache, kalau tidak ada jalankan closure dan simpan hasilnya.

Cache Tagging untuk Invalidasi yang Presisi

Masalah klasik caching adalah: kapan cache harus dihapus?

Laravel mendukung cache tags untuk mengelompokkan cache terkait sehingga bisa diinvalidasi sekaligus.

// Menyimpan cache dengan tag
Cache::tags(['products', 'catalog'])->remember('featured_products', 3600, function () {
    return Product::featured()->get();
});

// Menghapus semua cache bertag 'products' sekaligus
// Misalnya saat ada produk baru ditambahkan
Cache::tags('products')->flush();

Catatan penting: cache tags hanya bekerja dengan driver redis atau memcached, tidak dengan file atau database.

Hindari Cache Stampede

Cache stampede terjadi ketika banyak request masuk bersamaan tepat saat cache kedaluwarsa, dan semuanya langsung menghantam database.

Gunakan Cache::lock() untuk mencegah hal ini.

$value = Cache::remember('heavy_report', 3600, function () {
    return Cache::lock('heavy_report_lock', 10)->block(5, function () {
        // Hanya satu proses yang bisa masuk ke sini
        return DB::table('orders')
            ->selectRaw('DATE(created_at) as date, SUM(total) as revenue')
            ->groupBy('date')
            ->orderByDesc('date')
            ->limit(30)
            ->get();
    });
});

Memilih TTL yang Tepat

Tidak ada angka ajaib untuk TTL (time-to-live) cache.

Pertimbangkan seberapa sering data berubah dan seberapa kritis konsistensi datanya.

  • Data produk aktif: 1-6 jam
  • Data konfigurasi aplikasi: 24 jam atau hingga ada perubahan
  • Data dashboard real-time: 5-15 menit
  • Data yang berubah saat user melakukan aksi: cache dengan tag dan invalidasi manual

Kesalahan Schema Lainnya yang Sering Terlewat

Selain masalah-masalah utama di atas, ada beberapa kesalahan desain schema yang lebih subtle tapi sama berbahayanya.

Menggunakan VARCHAR Terlalu Panjang

Menetapkan VARCHAR(255) untuk semua kolom string tanpa berpikir adalah kebiasaan buruk.

Kolom dengan ukuran lebih kecil mengonsumsi memori lebih sedikit, terutama saat digunakan sebagai indeks.

Gunakan tipe data yang paling sesuai: TINYINT untuk status dengan nilai terbatas, CHAR untuk kode dengan panjang tetap, dan TEXT hanya jika kontennya memang panjang.

Menyimpan Banyak Data di Kolom JSON Tanpa Pertimbangan

Kolom JSON di MySQL dan PostgreSQL memang fleksibel dan menggoda untuk digunakan.

Tapi data yang disimpan di dalam JSON tidak bisa diindeks dengan efisien seperti kolom biasa.

Jika kamu sering melakukan query berdasarkan properti di dalam JSON, pertimbangkan untuk memisahkannya menjadi kolom tersendiri.

Tidak Menggunakan Soft Delete dengan Bijak

SoftDeletes di Laravel sangat praktis, tapi kamu perlu sadar bahwa Eloquent secara default menambahkan WHERE deleted_at IS NULL ke setiap query.

Pastikan kolom deleted_at diindeks, terutama di tabel yang besar dan sering diakses.

Schema::table('orders', function (Blueprint $table) {
    $table->index('deleted_at'); // Jangan lupa ini
});

Penutup

Performa aplikasi Laravel yang lambat jarang sekali diselesaikan dengan satu perbaikan tunggal.

Ini adalah kombinasi dari keputusan-keputusan kecil yang dibuat sejak awal: bagaimana relasi di-load, bagaimana indeks dirancang, kapan denormalisasi masuk akal, dan di mana caching bisa menolong.

Mulailah dengan mengaktifkan Telescope di environment staging, identifikasi query paling lambat, dan perbaiki satu per satu dari akar masalahnya.

Database yang dirancang dengan baik tidak hanya membuat aplikasi lebih cepat — ia juga membuat kode lebih mudah dipelihara dan di-scale ketika pengguna tumbuh.

Ajie Kusumadhany
Written by

Ajie Kusumadhany

Founder & Lead Developer KerjaKode. Berpengalaman dalam pengembangan web modern dengan Laravel, React.js, Vue.js, dan teknologi terkini. Passionate tentang coding, teknologi, dan berbagi pengetahuan melalui artikel.

Promo Spesial Hari Ini!

10% DISKON

Promo berakhir dalam:

00 Jam
:
00 Menit
:
00 Detik
Klaim Promo Sekarang!

*Promo berlaku untuk order hari ini

0
User Online
Halo! 👋
Kerjakode Support Online
×

👋 Hai! Pilih layanan yang kamu butuhkan:

Chat WhatsApp Sekarang