77 N+1 Problem dan Solusinya dengan DataLoader
N+1 problem adalah istilah klasik dalam pengembangan perangkat lunak ketika kita berurusan dengan akses ke data yang memiliki relasi, terutama di lingkungan ORM (Object Relational Mapping) atau query berbasis GraphQL/REST. Fenomena ini kerap menimbulkan masalah efisiensi yang berdampak pada performa aplikasi, dan seringkali baru tersadari setelah aplikasi live dan adanya keluhan performa dari end-user atau pemilik bisnis. Salah satu solusi yang naik daun dalam beberapa tahun terakhir adalah pendekatan DataLoader.
Artikel ini membedah 77 N+1 Problem (bukan hanya jumlah total kasus ya, melainkan ilustrasi dari betapa masifnya masalah ini!) dan menyoroti solusi efektif nan elegan menggunakan DataLoader. Kita akan mengupas secara rinci dengan simulasi kode, serta visualisasi arsitektur bagaimana DataLoader menyelamatkan kita dari jebakan kueri redundant.
Daftar Isi
- Apa itu N+1 Problem?
- Contoh Kasus dalam Aplikasi
- Dampak N+1 Problem
- Solusi: Eager Loading & DataLoader
- Implementasi DataLoader di Node.js
- Eksperimen dan Simulasi Kode
- Tabel Perbandingan
- Diagram Alur DataLoader
- Best Practices
- Kesimpulan
Apa itu N+1 Problem?
N+1 problem secara sederhana terjadi ketika pada saat mengambil resource/objek induk (N objek), lalu untuk tiap item juga melakukan query tambahan 1x ke resource relasi-nya. Alhasil, total query adalah N+1. Pada data yang besar, masalah ini menjadi membahayakan performa sistem.
Ilustrasi:
Misalkan kita ingin menampilkan daftar 10 artikel beserta penulisnya.
- Query 1:
SELECT * FROM articles LIMIT 10;
- Query 2-11: Satu per satu, untuk setiap artikel, ambil data penulis
SELECT * FROM authors WHERE id = ?;
Total ada 1+10 = 11 query hanya untuk data yang seharusnya bisa diambil jauh lebih efisien.
Contoh Kasus dalam Aplikasi
Katakan kita punya model Article & Author:
// Pseudo-code Model SQL
Article { id, title, author_id }
Author { id, name }
Kode tanpa optimasi (N+1 occuring):
const articles = await db.query('SELECT * FROM articles LIMIT 10');
for (const article of articles) {
article.author = await db.query('SELECT * FROM authors WHERE id = ?', [article.author_id]);
}
Jika data article ada 10, total query ke database = 11 kali.
Dampak N+1 Problem
- Overhead Database: Meningkat pesat, apalagi jika traffic tinggi.
- Latency meningkat: Tiap query ke DB butuh waktu (I/O), multiply N+1 bisa sangat lama.
- Scalability Problem: Membuat scaling menjadi mahal dan ribet.
- Cost: Koneksi database biasanya dibatasi, mengurangi performa seluruh aplikasi.
Tabel Dampak N+1
Jumlah Data | Query Normal | Query N+1 | Query Naif (tanpa batch) |
---|---|---|---|
1 | 1 | 2 | 2 |
10 | 1 | 11 | 11 |
100 | 1 | 101 | 101 |
1000 | 1 | 1001 | 1001 |
10000 | 1 | 10001 | 10001 |
Solusi: Eager Loading & DataLoader
Eager Loading
Eager loading adalah teknik ORM/SQL untuk mengambil data beserta relasinya sekali waktu, biasanya dengan JOIN atau sub-query.
Contoh:
SELECT articles.*, authors.*
FROM articles
JOIN authors ON authors.id = articles.author_id
DataLoader
DataLoader adalah library pattern populer (diciptakan oleh tim Facebook, sering dipakai di GraphQL API) untuk batching dan caching permintaan data yang sama atau sejenis dalam satu request/iterasi eventloop.
Fungsi utamanya:
- Batching: Mengumpulkan permintaan akses ke resource yang sama ke dalam satu batch query
- Caching: Menghindari query berulang untuk key yang sama pada satu request
Implementasi DataLoader di Node.js
Install library dataloader:
npm install dataloader
Setup DataLoader
const DataLoader = require('dataloader');
// Inisialisasi DataLoader
const authorLoader = new DataLoader(async (authorIds) => {
// Batch query
const rows = await db.query('SELECT * FROM authors WHERE id IN (?)', [authorIds]);
// Mapping hasil ke urutan authorIds
return authorIds.map(id => rows.find(row => row.id === id));
});
Menggunakan DataLoader
const articles = await db.query('SELECT * FROM articles LIMIT 10');
for (const article of articles) {
article.author = await authorLoader.load(article.author_id);
}
Jadi, meskipun ada 10 artikel, hanya ada 2 query:
- 1 query ambil articles
- 1 query (batch) ambil semua authors yang terkait
Eksperimen dan Simulasi Kode
Mari bandingkan dua pendekatan:
Simulasi sederhana dengan pseudo-kode dan console log.
// Without DataLoader (N+1 Problem)
const articles = await db.query('SELECT * FROM articles LIMIT 5');
// Query Counter
let queryCounter = 1;
for (const article of articles) {
queryCounter++;
article.author = await db.query('SELECT * FROM authors WHERE id = ?', [article.author_id]);
}
console.log('Total queries (tanpa DataLoader):', queryCounter);
// With DataLoader
const authorLoader = new DataLoader(async (authorIds) => {
queryCounter++;
const rows = await db.query('SELECT * FROM authors WHERE id IN (?)', [authorIds]);
return authorIds.map(id => rows.find(row => row.id === id));
});
for (const article of articles) {
article.author = await authorLoader.load(article.author_id);
}
console.log('Total queries (pakai DataLoader):', queryCounter);
Output:
Total queries (tanpa DataLoader): 6
Total queries (pakai DataLoader): 2
Tabel Perbandingan
Metode | Jumlah Query | Latency | I/O Database | Penjelasan |
---|---|---|---|---|
Naive/N+1 | N+1 | Tinggi | Tinggi | Query child dipanggil per-item |
Eager Loading | 1 | Rendah | Rendah | Query join |
DataLoader | 2 | Rendah | Rendah | Batch + cache per request |
Diagram Alur DataLoader
Mari visualisasikan dengan diagram Mermaid.
flowchart TD A[Minta daftar articles] --> B["SELECT * FROM articles"] B --> C{Loop setiap article} C --> D["DataLoader.load(article.author_id)"] D --> E[[Batch collect author_ids]] E --> F["SELECT * FROM authors WHERE id IN (author_ids)"] F --> G[Map hasil ke setiap article] G --> H[Tampilkan hasil]
DataLoader secara otomatis mendeteksi permintaan load multiple key, dan baru menjalankan batch query, lalu memetakan hasil ke request caller asinkron-nya.
Best Practices
- DataLoader per request: Inisialisasi DataLoader tiap request, bukan global singleton, agar cache tidak tercampur antar user/request.
- Batch size: Jika child data sangat banyak, bisa atur batch max size untuk mencegah query IN terlalu besar.
- Combining with Eager Loading: Untuk kasus tertentu join lebih efisien, pilih sesuai query pattern.
- Monitoring: Selalu profiling dan monitor query database.
Kesimpulan
N+1 problem adalah anti-pattern yang sering tidak disadari oleh developer, namun dampaknya signifikan pada performa aplikasi, utamanya pada sistem database relasional dan API GraphQL.
DataLoader adalah solusi pattern yang sederhana tapi powerful, dengan prinsip batching dan caching per request, sangat cocok untuk API modern yang memerlukan fleksibilitas dalam resolve data (misal GraphQL resolver). Dengan mengimplementasikan DataLoader, kita bisa memangkas ratusan atau bahkan ribuan query menjadi 2-3 query SQL saja!
Selalu review pola fetching data saat membangun aplikasi skalabel–pahami N+1 problem, dan gunakan pattern DataLoader setiap kali query per-relasi mulai terasa redundant.
Referensi
Selamat menulis kode yang lebih efisien dan scalable! 🚀
Artikel Terhangat
76 Optimasi Resolver agar Tidak Lambat
09 Sep 2025
97. Studi Kasus: Layanan Chat Real-Time
09 Sep 2025

76 Optimasi Resolver agar Tidak Lambat
