N+1 Problem & DataLoader
Problem terbesar di GraphQL: nested resolvers menyebabkan N+1 queries. 10 users dengan posts = 1 query users + 10 queries posts.
Problem
query {
users { # 1 query: SELECT * FROM users
name
posts { # N queries: SELECT * FROM posts WHERE author_id = ?
title # (dipanggil untuk SETIAP user)
}
}
}
# 10 users = 11 queries (1 + 10)
DataLoader
DataLoader batches dan caches individual loads. Semua .load(id) dalam satu tick di-batch menjadi satu query.
import DataLoader from "dataloader";
// Create loader (per request!)
const postsByAuthorLoader = new DataLoader(async (authorIds) => {
// Satu query untuk semua author IDs
const posts = await db.posts.findMany({
where: { authorId: { in: authorIds } },
});
// Return array of results, same order as authorIds
return authorIds.map(id => posts.filter(p => p.authorId === id));
});
// Resolver
User: {
posts: (parent) => postsByAuthorLoader.load(parent.id),
}
// Sekarang: 10 users = 2 queries (1 users + 1 batched posts)
Setup di Context
// PENTING: DataLoader harus per-request (bukan global)
context: ({ req }) => ({
user: getUser(req),
loaders: {
postsByAuthor: new DataLoader(batchPostsByAuthor),
commentsByPost: new DataLoader(batchCommentsByPost),
userById: new DataLoader(batchUsersById),
},
})
// Resolver
User: {
posts: (parent, _, { loaders }) => loaders.postsByAuthor.load(parent.id),
}
Rules
- Create DataLoader instances per-request (caching scoped to request)
- Batch function must return results in same order as keys
- Keys must be primitive (string, number)