N+1 Query

N+1 Query #

Dalam pengembangan web application yang berinteraksi dengan database, performa sering kali menjadi masalah laten yang baru terasa ketika trafik meningkat. Salah satu penyebab klasik yang sering diremehkan tapi berdampak besar adalah N+1 Query Problem.

Masalah ini tidak selalu terlihat jelas di awal development, terutama saat data masih sedikit. Namun di production, N+1 query bisa menjadi silent performance killer: aplikasi terasa lambat, database terbebani, dan biaya infrastruktur meningkat.

Artikel ini akan membahas secara mendalam:

  • Apa itu N+1 Query Problem
  • Kenapa dan bagaimana masalah ini muncul
  • Dampak nyata di web application
  • Berbagai pendekatan problem solving
  • Best practice untuk mencegah dan mengontrolnya

Apa Itu N+1 Query Problem? #

N+1 Query Problem terjadi ketika aplikasi:

  1. Menjalankan 1 query utama untuk mengambil data induk (parent)
  2. Lalu menjalankan N query tambahan untuk setiap data hasil query pertama

Secara sederhana:

Ambil daftar data → lalu untuk setiap data, ambil relasi dengan query terpisah

Contoh Sederhana #

Kasus: menampilkan daftar artikel beserta nama author-nya.

  1. Query pertama:
SELECT * FROM articles;

Menghasilkan N artikel.

  1. Untuk setiap artikel:
SELECT * FROM users WHERE id = ?;

Jika ada 100 artikel → total query = 1 + 100 = 101 query.

Inilah yang disebut N+1 query.


Kenapa N+1 Query Bisa Terjadi? #

ORM Abstraction yang Terlalu Nyaman #

ORM (GORM, Hibernate, ActiveRecord, Sequelize, dll) memudahkan developer dengan abstraction relasi.

Contoh pseudo-code:

articles := db.Find(&articles)
for _, a := range articles {
    fmt.Println(a.Author.Name)
}

Kelihatannya bersih, tapi di balik layar ORM bisa melakukan query tambahan setiap kali Author diakses.

Lazy Loading Default #

Banyak ORM menggunakan lazy loading secara default:

  • Relasi tidak diambil di awal
  • Baru di-query saat field relasi diakses

Jika tidak dikontrol, ini adalah sumber utama N+1.

Kurangnya Awareness di Code Review #

N+1 query:

  • Tidak selalu terlihat dari logic
  • Tidak menyebabkan error
  • Lolos unit test

Tanpa logging SQL atau profiling, masalah ini sering lolos sampai production.


Dampak N+1 Query di Web Application #

Performa Aplikasi Turun Drastis #

  • Banyak round-trip ke database
  • Latency meningkat
  • Response time tidak stabil

Yang tadinya:

  • 20 ms → bisa jadi 500–2000 ms

Beban Database Membengkak #

  • Query kecil tapi jumlahnya banyak
  • Connection pool cepat habis
  • Lock contention meningkat

Database sering jadi bottleneck, bukan application server.

Tidak Skalabel #

Aplikasi yang terlihat baik di:

  • 10 user

Akan runtuh di:

  • 1.000–10.000 user

Karena query tumbuh linear terhadap jumlah data, bukan terhadap request.

Biaya Infrastruktur Naik #

  • Perlu DB instance lebih besar
  • Read replica bertambah
  • Cache dipaksa bekerja lebih keras

Padahal akar masalahnya ada di pola query.


Cara Mendeteksi N+1 Query #

Enable SQL Logging #

Aktifkan log query di ORM:

  • Lihat apakah ada pola query yang berulang
  • Query yang sama dieksekusi puluhan/ratusan kali

Gunakan Query Profiler #

  • APM (Datadog, New Relic)
  • Slow query log database

Cari:

  • Banyak query kecil
  • Total query count tinggi untuk satu request

Load Test dengan Data Realistis #

N+1 sering tidak muncul di data kecil.

Gunakan:

  • Jumlah data mendekati production
  • Traffic simulasi

Problem Solving N+1 Query #

Eager Loading (Preloading) #

Ambil relasi sekaligus di awal.

Contoh (konsep umum):

SELECT articles.*, users.*
FROM articles
JOIN users ON users.id = articles.user_id;

Atau ORM style:

db.Preload("Author").Find(&articles)

Hasil:

  • Tetap 1 atau 2 query
  • Tidak tergantung jumlah data

Batch Query / IN Query #

Daripada query per item:

SELECT * FROM users WHERE id = 1;
SELECT * FROM users WHERE id = 2;

Gunakan:

SELECT * FROM users WHERE id IN (1,2,3,...);

ORM biasanya menyediakan mekanisme ini jika digunakan dengan benar.

DTO / Projection Query #

Jangan selalu load full object.

Contoh:

  • Hanya butuh article.title dan author.name

Gunakan query spesifik:

SELECT a.title, u.name
FROM articles a
JOIN users u ON u.id = a.user_id;

Lebih hemat:

  • Memory
  • Network
  • CPU

Cache Relasi yang Stabil #

Untuk data yang:

  • Jarang berubah
  • Dibaca sering

Gunakan:

  • In-memory cache
  • Redis

Tapi cache bukan solusi utama, hanya optimasi tambahan.


Best Practice Menghindari N+1 Query #

Selalu Curiga pada Loop + Database Access #

Rule of thumb:

Jika ada loop dan di dalamnya ada akses DB, alarm harus berbunyi.

Default ke Eager Loading #

Jika relasi hampir selalu digunakan:

  • Lebih aman eager load
  • Lazy loading hanya untuk kasus khusus

Review Query Count di Code Review #

Best practice tim:

  • Tanyakan: “Ini query-nya berapa kali per request?”
  • Minta bukti SQL log jika perlu

Gunakan Boundary yang Jelas antara Domain & Persistence #

Pisahkan:

  • Business logic
  • Query logic

Repository/service yang eksplisit membuat pola query lebih terlihat dan terkontrol.

Monitoring di Production #

Pasang alert untuk:

  • Query count per request
  • Slow query
  • DB CPU & connection pool usage

N+1 sering baru terlihat di production, jadi observability itu wajib.


Penutup #

N+1 Query Problem bukan sekadar isu teknis kecil, tapi masalah desain data access. Ia sering muncul karena:

  • Abstraction yang terlalu nyaman
  • Kurangnya visibilitas terhadap query

Dengan:

  • Awareness
  • Pola query yang benar
  • Code review dan monitoring yang disiplin

N+1 query bisa dicegah sejak awal dan tidak menjadi bom waktu di production.

Ingat: database itu mahal, setiap query harus punya alasan yang jelas.

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact