Pengembang sering menanyakan hal yang sama kepada saya: "API saya lambat. Dari mana saya harus mulai?" Refleksnya biasanya adalah meningkatkan server atau menggandakan RAM. Itu memakan biaya dan jarang memperbaiki akar masalahnya. Di sebagian besar aplikasi Laravel, hambatan (bottleneck) berada di lapisan database. Sintaks framework yang elegan membuatnya mudah untuk lupa bahwa setiap panggilan Eloquent pada akhirnya menjadi SQL, dan SQL sering kali menjadi awal dari masalah tersebut.
Sebelum Anda menyentuh pengaturan server, periksalah kueri Anda secara metodis.
Mulailah dengan Diagnostik yang Tepat
Jangan melakukan optimasi dalam kegelapan. Menulis ulang kueri secara acak hanyalah tebakan, dan tebakan membuang-buang waktu.
Anda perlu menemukan pernyataan (statements) yang mengonsumsi total waktu paling banyak. Pantau aplikasi Anda di bawah beban nyata. Laravel Telescope memberi Anda tampilan bersih dari setiap kueri yang dijalankan selama permintaan, lengkap dengan informasi waktunya. Laravel Debugbar menampilkannya di browser Anda selama pengembangan lokal sehingga Anda dapat segera mendeteksi anomali. Saat Anda perlu menangkap masalah di produksi, aktifkan MySQL Slow Query Log. Fitur ini mencatat pernyataan yang melebihi ambang batas yang Anda tentukan, sehingga ideal untuk menemukan kejutan yang tidak muncul pada dataset kecil. Jika Anda menjalankan sesuatu yang lebih besar, alat Application Performance Monitoring dapat mengorelasikan endpoint HTTP yang lambat dengan panggilan database tertentu.
Saat Anda meninjau data, carilah dua hal: waktu eksekusi absolut dan frekuensi panggilan. Kueri yang memakan waktu empat puluh milidetik terdengar tidak berbahaya sampai Anda menyadari bahwa kueri tersebut berjalan dua ribu kali per menit. Laporan berdurasi tiga detik yang berjalan sekali per jam mungkin kurang penting dibandingkan pencarian setengah detik yang berjalan di setiap halaman. Perbaiki masalah dengan dampak tinggi terlebih dahulu.
Berhenti Meminta Segalanya
SELECT * memang praktis. Namun, itu juga mahal. Saat Anda menulis Model::all() atau mengambil set hasil tanpa menyebutkan nama kolom, MySQL menarik setiap kolom untuk setiap baris yang cocok. Ini termasuk kolom teks besar, blob JSON, dan apa pun yang ada di tabel tersebut. Set hasil membengkak, penggunaan memori meningkat, dan waktu yang dihabiskan untuk melakukan serialisasi respons pun bertambah.
Jadilah eksplisit. Jika controller Anda hanya membutuhkan kolom id, name, dan email, mintalah kolom-kolom tersebut secara spesifik:
User::select('id', 'name', 'email')->get();
Dalam query builder, prinsip yang sama berlaku. Payload yang lebih kecil berpindah lebih cepat melalui jaringan dan mengonsumsi lebih sedikit RAM pada server aplikasi Anda. Ini adalah salah satu cara termudah dan termurah untuk mendapatkan peningkatan performa, namun sering kali terabaikan karena Laravel menjadikan SELECT * sebagai perilaku default.
Biarkan EXPLAIN Memandu Perubahan Anda
Jangan pernah melakukan refaktor pada kueri yang lambat tanpa menjalankan EXPLAIN terlebih dahulu. Di MySQL, kata kunci EXPLAIN menunjukkan rencana eksekusi kueri. Ini mengungkapkan secara tepat bagaimana optimizer bermaksud menemukan data Anda.
Perhatikan kolom type. Jika Anda melihat ALL, MySQL sedang melakukan pemindaian tabel secara penuh (full table scan). Itu berarti MySQL membaca setiap baris untuk memenuhi klausa WHERE Anda. Lihat kolom key untuk melihat apakah optimizer menggunakan indeks sama sekali. Kemudian periksa kolom Extra. Jika Anda melihat Using temporary atau Using filesort, MySQL sedang membangun tabel perantara atau melakukan pengurutan di memori karena struktur Anda saat ini tidak dapat memenuhi kueri dengan efisien.
Jalankan EXPLAIN di klien MySQL Anda, atau gunakan alat yang memformat output untuk Anda. Setelah Anda melihat rencananya, Anda akan tahu apakah masalahnya adalah indeks yang hilang, join yang buruk, atau predikat yang tidak dapat dioptimalkan oleh engine. Menebak-nebak tidak lagi diperlukan.
Gunakan Indeks dengan Terencana
Indeks adalah alat paling ampuh untuk mempercepat pencarian, tetapi mereka hanya bekerja jika sesuai dengan cara Anda melakukan kueri. Tanpa indeks yang tepat, MySQL akan memindai baris demi baris. Hal ini mungkin terasa baik-baik saja saat pengembangan pada tabel dengan seribu baris, namun bisa berantakan di produksi pada tabel dengan sepuluh juta baris.
Mulailah dengan indeks kolom tunggal untuk kolom yang sering muncul dalam klausa WHERE. Jika Anda terus-menerus memfilter berdasarkan status, tambahkan indeks pada status.
Ketika sebuah kueri memfilter beberapa kolom sekaligus, beralihlah ke indeks komposit. Urutan kolom di dalam indeks sangat penting karena MySQL membaca indeks komposit dari kiri ke kanan. Ini disebut aturan leftmost prefix. Jika kueri Anda mencari berdasarkan user_id dan kemudian mengurutkan berdasarkan created_at, indeks komposit pada (user_id, created_at) akan sangat membantu. Balik urutannya, dan optimizer mungkin tidak akan menggunakan indeks tersebut untuk filter sama sekali.
Jangan mengindeks setiap kolom. Setiap indeks menambah beban (overhead) pada proses insert, update, dan delete karena MySQL harus memelihara strukturnya. Tambahkan indeks secara sengaja berdasarkan pola yang Anda temukan selama pengukuran.
Jauhkan Fungsi dari Kolom
Kesalahan ini secara diam-diam menonaktifkan indeks. Saat Anda membungkus kolom dalam sebuah fungsi di dalam klausa WHERE, MySQL tidak dapat
