N + 1 Query: Pembunuh Kecepatan Aplikasi

3 menit

Database seringkali menjadi bottleneck atau penghambat dari performa aplikasi.

Terdapat banyak aspek yang dapat kita optimalisasikan untuk bisa meningkatkan performanya.

Salah satu yang paling mudah untuk dilakukan (dan juga masalah yang cukup sering terjadi) adalah dengan mengatasi N+1 Query Problem.

Apa itu N+1 Query Problem?

Mari kita bayangkan skenario seperti berikut:

Kita memiliki sebuah online shop yang memiliki banyak barang (Item).

Kemudian, setiap barang memiliki beberapa gambar (Image) yang mendeskripsikan barang tersebut.

Tentunya, dari kasus ini, relasi keduanya adalah berupa One-to-Many (sebuah barang dapat memiliki banyak gambar).

Selanjutnya, bayangkan kita ingin membuat sebuah halaman utama (home, misal) yang akan menampilkan semua barang yang ada di online shop milik kita.

Maka controller Laravel-nya yang dibuat kurang lebih akan seperti ini:

<?php

namespace App\Http\Controllers;

use App\Models\Item;

class Home extends Controller
{
    public function __invoke()
    {
      $items = Item::all();
      return view('home', ['items' => $items]);
    }
}

Kemudian untuk file Blade template-nya sendiri:

<ul>
  @foreach ($items as $item)
    <li>
      Item {{ $item->id }}, with images:

      @foreach ($item->images as $image)
        {{ $image->id }},
      @endforeach
    </li>
  @endforeach
</ul>

Kode di atas akan mengambil semua barang yang ada di database beserta setiap gambarnya yang kemudian akan ditampilkan di browser.

Terlihat tidak ada yang salah? Iya, sekilas.

Tapi sayangnya, kini aplikasi kita berhadapan dengan N+1 Query Problem.

Bagaimana bisa?

Pertama, mari kita sepakati bahwa mengakses database cukup menghabiskan banyak waktu, sehingga tentunya kita ingin meminimalisir jumlah query yang dikirimkan oleh aplikasi menuju database.

Problemnya adalah, ketika kita menuliskan logika seperti berikut:

<?php

$items = Item::all();

foreach ($items as $item) {
  // mengakses relationship dari $item
  $item->images;
}

Database akan membuat:

  • 1 buah query untuk memperoleh data $items
  • N buah query untuk mengambil $images dari setiap $items (dengan mengasumsikan jumlah $items adalah N)

Oleh karenanya, masalah ini disebut sebagai N+1 Query Problem.

Sebagai gambaran, jika jumlah Item yang ada di online shop kita memiliki 100 data barang yang berbeda, maka app akan mengirimkan 101 query menuju database setiap kali ada pengunjung yang mengakses halaman home dari website ini!

Maka tidak heran jika online shop ini akan menjadi semakin lambat seiring dengan meningkatnya jumlah barang dan pengguna.

Bagaimana cara memperbaiki masalah ini?

Cukup simpel, kita hanya perlu untuk menggunakan eager loading.

Dengan menggunakan teknik ini, kita memberikan arahan kepada Eloquent ORM untuk memuat suatu data relationship tertentu secara langsung.

Cara menggunakannya adalah dengan mengganti bagian ini:

$items = Item::all();

Menjadi:

# Kita menggunakan fungsi `with` untuk melakukan eager loading
$items = Item::with('images')->get();

Itu saja!

Perbedaannya adalah dengan menggunakan eager loading, query yang diperlukan untuk menangani kasus sebelumnya hanya membutuhkan 2 buah query saja, yakni:

  • Mengakses data $items
  • Mengakses data $images milik semua $items secara langsung

Tentunya penurunan jumlah query ini akan sangat berdampak pada meningkatnya performa keseluruhan dari aplikasi.

Penutup

N+1 Query Problem adalah sebuah permasalahan yang cukup sering dijumpai developer, baik bagi yang pemula ataupun yang sudah berpengalaman.

Pada problem N+1 query ini, tidak ada yang error terjadi, namun performa aplikasi akan menurun seiring dengan semakin meningkatnya jumlah baris data di database. Sehingga seringkali pada proses development masalah ini sulit terdeteksi.

Untuk membantu mencegah terjadinya problem ini, kita bisa menggunakan sebuah package Laravel yang bernama laravel-query-detector yang akan mendeteksi secara otomatis dan menampilkan peringatan kepada developer ketika terdapat operasi yang menggunakan N+1 query.

Sekian dan terima kasih👋