Coreframe Labs

Automated performance audit

Performance audits for Prisma ORM

Prisma's type safety is excellent and tells you nothing about how many queries a call makes. A nested `include` reads like a single fetch and can execute as several — and the ones that hurt only hurt at production row counts.

Check a file now — free

Paste one file. The free tier runs a single security pass and returns up to three findings, each with the line of code that evidences it. Nothing is stored, nothing is used for training, and detected secrets are redacted before analysis.

Paste one file0 / 400 lines

Not stored. Not trained on. Secrets redacted before analysis.

What we find most often in Prisma ORM

Each of these is a real pattern, with the code that causes it and the change that fixes it.

1. Nested include fan-out

Symptom
One `findMany` in the code. Six queries and a large payload in the logs.

The pattern

const orgs = await db.org.findMany({
  include: {
    users: { include: { posts: { include: { comments: true } } } },
  },
});

Why it bites
Prisma resolves each relation level as a separate query and joins in memory. Three levels across a hundred orgs materializes every comment for every post for every user — usually to render a count. Payload size, not query time, is what breaks it.

The fix

// Ask for the shape you actually render.
const orgs = await db.org.findMany({
  select: {
    id: true,
    name: true,
    _count: { select: { users: true } },   // aggregate in the database
  },
});

2. Queries inside a loop

Symptom
A batch job's runtime scales linearly with input size.

The pattern

for (const id of userIds) {
  const user = await db.user.findUnique({ where: { id } });
  results.push(user);
}

Why it bites
Each iteration is a full round trip, serialized by `await`. A thousand IDs is a thousand sequential network hops — dominated entirely by latency, not by the database's work.

The fix

const users = await db.user.findMany({ where: { id: { in: userIds } } });
const byId = new Map(users.map((u) => [u.id, u]));

3. Offset pagination on a large table

Symptom
Page 1 is instant. Page 5,000 takes several seconds.

The pattern

const page = await db.event.findMany({ skip: 100_000, take: 20 });

Why it bites
`OFFSET 100000` makes the database read and discard a hundred thousand rows before returning twenty. Cost grows with page depth, so the problem appears only once there is enough data to page that deep.

The fix

// Cursor pagination — constant time at any depth.
const page = await db.event.findMany({
  take: 20,
  skip: 1,
  cursor: { id: lastSeenId },
  orderBy: { id: "asc" },
});

4. Interactive transactions holding a connection open

Symptom
Pool exhaustion under load, with most connections idle in transaction.

The pattern

await db.$transaction(async (tx) => {
  const order = await tx.order.create({ data });
  await sendConfirmationEmail(order);   // external HTTP call, ~800ms
  await tx.order.update({ where: { id: order.id }, data: { notified: true } });
});

Why it bites
The transaction holds a connection and its row locks for the entire duration, including the network call to the email provider. One slow third party stalls the pool for everyone, and Prisma's default 5-second transaction timeout turns it into rollbacks.

The fix

// Commit fast; do the slow, non-transactional work after.
const order = await db.$transaction(async (tx) => tx.order.create({ data }));

await sendConfirmationEmail(order);
await db.order.update({ where: { id: order.id }, data: { notified: true } });

An automated pass reads one file. An audit reads the system.

The tool above is deliberately narrow — one file, one dimension, one pass. The issues that actually take systems down live between files: in the trust boundary nobody documented, the query pattern that only emerges at scale, the cache that was correct until the second tenant arrived. That is what the Coreframe Technical Audit is for — five business days, a written report, a live findings call, and a remediation proposal you can execute with or without us.

£1,500–£2,500 solo · £3,000–£5,000 funded startup · £3,000–£6,000 SME

See what the full audit covers →

Related audits

All stack audits →