An endpoint that lists orders with user info attached. The diff was 14 lines. The bug was the canonical N+1: one query for the orders, one query per order to fetch its user. Mesrai's review fired before the PR's first reviewer arrived.
The vulnerable diff
// api/orders.ts
ordersRouter.get("/orders", async (req, res) => {
const orders = await db.orders.findMany({ where: { ownerId: req.user.id } });
// BUG: one query per order
for (const o of orders) {
o.user = await db.users.findUnique({ where: { id: o.userId } });
}
res.json(orders);
});What is wrong
N+1 is the most common database performance bug in 2026 — the same shape was old when ActiveRecord made it famous. One query loads N rows; the application then loops and runs one query per row to fetch related data. Total: N+1 queries. The fix is to join in the database, fetch related rows in a single `WHERE id IN (...)`, or use a DataLoader that batches per-tick.
The attack
Reproduces immediately under realistic data:
# 200 orders, 200 user lookups, 1 outer query = 201 queries
# Postgres EXPLAIN ANALYZE on 200 individual selectByPk lookups:
# total time: 1,840ms (mostly round-trip latency, 9ms each)
# Replace with a single JOIN:
# total time: 14msLatency scales linearly with row count. The endpoint feels fast in dev with 5 orders, falls over in production with 500.
Mesrai's review comment
mesraipilot · Bot · reviewed 90 sec ago
[mesrai] [code-review] [Performance] [N+1] [high]
N+1 query. One select for orders + one per order for users.
Total queries scale with row count: 200 orders → 201 queries.
Fix with an ORM include (joins under the hood):
const orders = await db.orders.findMany({
where: { ownerId: req.user.id },
include: { user: true },
});
Or with explicit IN-clause batching:
const orders = await db.orders.findMany({ where: ... });
const userIds = orders.map(o => o.userId);
const users = await db.users.findMany({ where: { id: { in: userIds } } });
// attach back: const userMap = new Map(users.map(u => [u.id, u]));The fix
// api/orders.ts — fixed
ordersRouter.get("/orders", async (req, res) => {
const orders = await db.orders.findMany({
where: { ownerId: req.user.id },
include: { user: true },
});
res.json(orders);
});The ORM's `include` produces a single JOIN. All rows in one round-trip. For GraphQL or many-to-many cases where include is not enough, use DataLoader to batch lookups within the same event-loop tick.
Why human review missed it
N+1 looks like a normal loop. Reviewers see the iteration as application logic and miss that each iteration is a database round-trip. The bug also passes tests on small fixtures. Mesrai catches it by flagging every `await db.*.find*` inside a loop.
Related rules + further reading
Mesrai rule pack: performance/no-n-plus-one — flags database calls inside loops.
Prisma docs: eager loading with `include`.
Classic ActiveRecord term, applicable to every ORM since.
Takeaway
Database calls in loops. The single most common performance bug. Mesrai catches every one — include, IN, or DataLoader.