Globhy
HAHartanto3 hours ago2 views

Share:

N+1 Query Problem: Salah Satu Penyebab Aplikasi Lambat yang Sering Tidak Disadari

Technology

Pelajari apa itu N+1 Query Problem, penyebab, dampaknya terhadap performa aplikasi, serta berbagai cara mengatasinya menggunakan JavaScript, Node.js, ORM seperti Prisma, Sequelize, TypeORM, hingga teknik optimasi query database.

N+1 Query Problem: Salah Satu Penyebab Aplikasi Lambat yang Sering Tidak Disadari

Saat membangun aplikasi backend menggunakan JavaScript atau Node.js, kita sering memanfaatkan ORM (Object Relational Mapping) seperti Prisma, Sequelize, TypeORM, MikroORM, atau bahkan fitur populate pada Mongoose. ORM membuat interaksi dengan database menjadi jauh lebih mudah dan kode menjadi lebih bersih.

Namun, kemudahan tersebut juga memiliki konsekuensi. Salah satu masalah performa yang paling sering muncul adalah N+1 Query Problem.

Yang membuat masalah ini berbahaya adalah karena kode aplikasi terlihat sederhana dan berjalan dengan benar. Tidak ada error, tidak ada warning, tetapi performa aplikasi perlahan menurun seiring bertambahnya jumlah data.

N+1 Query Problem: Memahami Penyebab, Dampak, dan Cara Mengatasinya

Pada artikel ini kita akan membahas secara mendalam apa itu N+1 Query Problem, mengapa hal tersebut terjadi, bagaimana cara mendeteksinya, serta berbagai strategi untuk mengatasinya.

Apa Itu N+1 Query Problem?

N+1 Query Problem adalah kondisi ketika aplikasi menjalankan 1 query utama, kemudian menjalankan N query tambahan untuk mengambil data yang berkaitan dengan setiap hasil dari query pertama.

Secara sederhana:

Misalnya terdapat 100 data utama.

Alih-alih hanya menjalankan satu atau dua query, aplikasi justru menjalankan:

Total menjadi 101 query.

Semakin besar jumlah data, semakin besar pula jumlah query yang dikirim ke database.

Contoh Kasus

Misalkan kita memiliki dua tabel.

Users

id

name

1

Alice

2

Bob

3

Charlie

Posts

id

title

user_id

Setiap user dapat memiliki banyak post.

Kode menggunakan Prisma:

javascript
const users = await prisma.user.findMany()

for (const user of users) {
const posts = await prisma.post.findMany({
where: {
userId: user.id
}
})

console.log(posts.length)
}

Sekilas kode tersebut terlihat normal.

Namun di balik layar ORM akan menjalankan SQL seperti berikut.

javascript
SELECT * FROM users;

Kemudian:

javascript
SELECT * FROM posts WHERE user_id = 1;
javascript
SELECT * FROM posts WHERE user_id = 2;
javascript
SELECT * FROM posts WHERE user_id = 3;

Jika terdapat 500 user maka akan terjadi:

Total menjadi 501 query.

Mengapa Disebut N+1?

Huruf N melambangkan jumlah data yang diperoleh dari query pertama.

Misalnya:

Artinya jumlah query akan terus bertambah secara linear mengikuti banyaknya data.

Mengapa N+1 Query Berbahaya?

Banyak developer menganggap query database adalah operasi yang murah. Padahal yang mahal bukan hanya eksekusi SQL.

Setiap query melibatkan proses seperti:

  1. aplikasi mengirim request ke database,
  2. database menerima koneksi,
  3. SQL diparsing,
  4. query planner menentukan execution plan,
  5. data diambil,
  6. hasil dikirim kembali melalui jaringan,
  7. ORM mengubah hasil menjadi objek JavaScript.

Proses tersebut terjadi pada setiap query.

Bayangkan jika satu query membutuhkan waktu rata-rata 3 ms.

1001 query berarti secara teori membutuhkan sekitar 3 detik hanya untuk komunikasi dengan database, belum termasuk proses lainnya.

Inilah mengapa aplikasi sering terasa lambat ketika jumlah data bertambah.

Mengapa ORM Sangat Rentan Mengalami N+1?

ORM berusaha membuat relasi database terlihat seperti properti JavaScript biasa.

Misalnya:

javascript
article.author

Terlihat seperti mengakses sebuah properti.

Padahal sebenarnya ORM mungkin sedang menjalankan query baru ke database.

Begitu pula ketika memanggil:

javascript
user.posts

atau

javascript
post.comments

Setiap akses relasi berpotensi menghasilkan query baru.

Semakin banyak data yang diproses dalam loop, semakin besar kemungkinan muncul N+1 Query Problem.

Lazy Loading vs Eager Loading

N+1 Query Problem biasanya muncul karena penggunaan lazy loading.

Lazy Loading

Relasi baru diambil ketika benar-benar diakses.

javascript
const users = await prisma.user.findMany()

for (const user of users) {
await prisma.post.findMany({
where: {
userId: user.id
}
})
}

Setiap iterasi menghasilkan query baru.

Eager Loading

Relasi diambil sejak awal.

javascript
const users = await prisma.user.findMany({
include: {
posts: true
}
})

Dengan eager loading, ORM dapat mengoptimalkan proses pengambilan data sehingga tidak perlu menjalankan query untuk setiap user secara terpisah.

Cara Mendeteksi N+1 Query Problem

Beberapa tanda yang sering muncul antara lain:

Sebagian besar ORM juga menyediakan fitur query logging.

Misalnya Prisma:

javascript
new PrismaClient({
log: ["query"]
})

Dengan logging aktif, Anda dapat melihat seluruh query yang dijalankan dan dengan cepat mengenali pola N+1.

Cara Mengatasi N+1 Query Problem

1. Gunakan Eager Loading

Cara paling umum adalah menggunakan eager loading.

Prisma:

javascript
const posts = await prisma.post.findMany({
include: {
author: true,
comments: true
}
})

Sequelize:

javascript
include: [User]

TypeORM:

javascript
relations: {
author: true
}

2. Gunakan JOIN

Dalam SQL, JOIN sering menjadi solusi terbaik.

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

Daripada menjalankan ratusan query, cukup satu query yang mengambil seluruh data yang diperlukan.

3. Gunakan Batch Query

Daripada:

javascript
SELECT * FROM posts WHERE user_id = 1;
SELECT * FROM posts WHERE user_id = 2;
SELECT * FROM posts WHERE user_id = 3;

Gunakan:

javascript
SELECT *
FROM posts
WHERE user_id IN (1,2,3);

Kemudian kelompokkan hasilnya di sisi aplikasi.

Teknik ini banyak digunakan oleh GraphQL DataLoader.

4. Hindari Query di Dalam Loop

Contoh buruk:

javascript
for (const user of users) {
await prisma.post.findMany(...)
}

Lebih baik kumpulkan seluruh ID terlebih dahulu.

javascript
const ids = users.map(u => u.id)

Lalu lakukan satu query menggunakan IN.

5. Gunakan DataLoader pada GraphQL

GraphQL sangat rentan terhadap N+1 Query Problem karena setiap resolver dapat memanggil database secara independen.

DataLoader melakukan batching dan caching sehingga beberapa permintaan dapat digabung menjadi satu query.

Apakah JOIN Selalu Lebih Baik?

Tidak selalu.

JOIN juga memiliki konsekuensi.

Misalnya satu user memiliki:

JOIN dapat menghasilkan jutaan baris sementara sebagian besar datanya merupakan duplikasi.

Pada kondisi seperti ini, kombinasi beberapa query yang terencana sering kali lebih efisien dibandingkan satu JOIN yang sangat besar.

Optimasi database selalu bergantung pada kebutuhan aplikasi.

Apakah MongoDB Bisa Mengalami N+1?

Ya.

Walaupun MongoDB bukan database relasional, masalah yang sama tetap dapat terjadi.

Contohnya:

javascript
for (const post of posts) {
await User.findById(post.author)
}

Kode tersebut tetap menghasilkan satu query untuk setiap dokumen.

Solusinya dapat menggunakan:

javascript
populate()

atau mengambil seluruh author menggunakan satu query berdasarkan daftar ID.

Kapan N+1 Tidak Menjadi Masalah?

Tidak semua N+1 harus dioptimalkan.

Misalnya:

Dalam kondisi tersebut, kode yang lebih sederhana terkadang lebih bernilai dibandingkan optimasi yang berlebihan.

Prinsip yang baik adalah mengoptimalkan berdasarkan hasil pengukuran, bukan asumsi.

Best Practices

Untuk menghindari N+1 Query Problem pada aplikasi JavaScript atau Node.js, beberapa praktik yang direkomendasikan adalah:

Kesimpulan

N+1 Query Problem merupakan salah satu penyebab utama menurunnya performa aplikasi backend, terutama ketika menggunakan ORM. Masalah ini muncul karena aplikasi menjalankan satu query utama yang diikuti oleh banyak query tambahan untuk setiap data yang diperoleh.

Walaupun aplikasi tetap berfungsi dengan benar, jumlah query yang terus bertambah akan meningkatkan waktu respons, beban jaringan, serta penggunaan sumber daya database.

Dengan memahami cara kerja lazy loading, eager loading, JOIN, batch query, dan teknik seperti DataLoader, developer dapat membangun aplikasi yang lebih efisien, lebih mudah diskalakan, dan mampu menangani pertumbuhan data tanpa mengalami penurunan performa yang signifikan.

Sebagai aturan praktis, biasakan untuk memeriksa query yang dihasilkan oleh ORM. Kode yang terlihat sederhana belum tentu menghasilkan SQL yang efisien. Semakin dini N+1 Query Problem terdeteksi, semakin mudah pula untuk memperbaikinya sebelum menjadi bottleneck di lingkungan produksi.

Share:

More in Technology

View category