Optimizing Prisma Performance
1. Reducing N+1 Queries
// Bad — N+1
for (const u of users) await prisma.post.findMany({ where: { authorId: u.id } });
// Good — single query
await prisma.user.findMany({ include: { posts: true } });
| Strategy | Detail |
|---|---|
| include / select | Eager load relations |
| in() | Batch IDs in one query |
| DataLoader | Batch + cache per request |
2. Using Select to Limit Fields
| Benefit | Detail |
|---|---|
| Smaller payload | Less network |
| Index-only scans | If select fits covering index |
3. Implementing Query Caching
| Layer | Detail |
|---|---|
| Accelerate | TTL/SWR cache via Prisma |
| App cache | Redis/in-memory |
| HTTP cache | Cache GET endpoints |
4. Using Connection Pooling
| Option | Detail |
|---|---|
| connection_limit | Tune per worker |
| PgBouncer | External pool (use ?pgbouncer=true) |
| Accelerate | Managed pooler |
5. Implementing Pagination Properly
| Method | Use |
|---|---|
| Cursor | Scales to millions |
| Keyset | Stable order, indexed |
| Skip/take | Small offsets only |
6. Optimizing Includes
| Tip | Detail |
|---|---|
| Nested select | Trim fields inside includes |
| Avoid deep nesting | Multiple shallow queries can be faster |
| take inside include | Limit child rows |
7. Using Batching
const [users, posts] = await prisma.$transaction([
prisma.user.findMany(),
prisma.post.findMany()
]);
| Aspect | Detail |
|---|---|
| Array tx | Single round-trip |
| DataLoader | Per-request batching |
8. Implementing Index Optimization
| Action | Detail |
|---|---|
| Cover filter+order | Composite index |
| Partial indexes | Filter common predicate |
| Drop unused | Reduce write cost |
9. Using Read Replicas
| Aspect | Detail |
|---|---|
| Extension | @prisma/extension-read-replicas |
| Routing | Reads → replicas, writes → primary |
| Lag | Accept eventual consistency |
10. Monitoring Performance Metrics
| Source | Detail |
|---|---|
prisma.$metrics.json() | Counters/histograms |
| Prometheus | prisma.$metrics.prometheus() |
| OTEL | Spans + metrics export |