100 items, 101 queries: el patrón N+1 y cómo eliminarlo.
El N+1 aparece cada vez que ponés un `await` adentro de un `for`. Cómo detectarlo en los logs y reemplazarlo por una sola query.
Un endpoint lista 100 stores y tarda 3 segundos. El log de la base muestra 101 queries por request: una para traer los stores, cien más para contar reviews. El usuario lo siente como "lento hoy". La base lo siente como CPU al 80%.
El patrón es viejo y se llama N+1. Aparece cada vez que hay un await adentro de un for o un forEach: una query inicial trae N items, y por cada item se dispara una query más para "enriquecerlo".
// ❌ N+1: 101 queries para 100 stores
const stores = await prisma.store.findMany()
for (const store of stores) {
const reviews = await prisma.review.findMany({
where: { storeId: store.id },
})
store.reviewCount = reviews.length
}
// ✅ Una query, el ORM hace el join + agregación
const stores = await prisma.store.findMany({
include: {
_count: { select: { reviews: true } },
},
})
Tres segundos pasan a 80 milisegundos. Más importante: el costo deja de escalar con los datos. 100 stores o 10.000, una sola query.
El primo del N+1: queries secuenciales que podrían ser paralelas
El otro patrón que se le parece sin serlo: tres awaits seguidos que no dependen uno de otro.
// ❌ Secuencial, tiempo total = suma
const store = await getStore(id) // 200ms
const products = await getProducts(id) // 300ms
const analytics = await getAnalytics(id) // 500ms
// Total: 1000ms
// ✅ Paralelo, tiempo total = el más lento
const [store, products, analytics] = await Promise.all([
getStore(id),
getProducts(id),
getAnalytics(id),
])
// Total: 500ms
Antes de optimizar CPU, eliminá round-trips de red. La latencia de red es casi siempre el bottleneck real. Un Promise.all te puede ahorrar más tiempo que una semana de micro-optimización.
Cómo detectarlo sin leer código
Corré la request y contá las queries en el log de la base. Si el número de queries crece con el volumen de datos, tenés N+1. Si es constante, estás bien.
Es la auditoría de performance más simple para un endpoint que no escribiste vos.