311 lines
5.9 KiB
Markdown
311 lines
5.9 KiB
Markdown
# Common Scenarios
|
|
|
|
Real-world scenarios and solutions for PrismaClient singleton pattern.
|
|
|
|
## Scenario 1: Converting Existing Codebase
|
|
|
|
**Current state:** Multiple files create their own PrismaClient
|
|
|
|
**Steps:**
|
|
|
|
1. Create central singleton: `lib/db.ts`
|
|
|
|
```typescript
|
|
import { PrismaClient } from '@prisma/client'
|
|
|
|
const globalForPrisma = globalThis as unknown as {
|
|
prisma: PrismaClient | undefined
|
|
}
|
|
|
|
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
|
|
|
|
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
|
|
```
|
|
|
|
2. Use Grep to find all `new PrismaClient()` calls:
|
|
|
|
```bash
|
|
grep -rn "new PrismaClient()" --include="*.ts" --include="*.js" .
|
|
```
|
|
|
|
3. Replace with imports from `lib/db.ts`:
|
|
|
|
**Before:**
|
|
|
|
```typescript
|
|
import { PrismaClient } from '@prisma/client'
|
|
const prisma = new PrismaClient()
|
|
|
|
export async function getUsers() {
|
|
return await prisma.user.findMany()
|
|
}
|
|
```
|
|
|
|
**After:**
|
|
|
|
```typescript
|
|
import { prisma } from '@/lib/db'
|
|
|
|
export async function getUsers() {
|
|
return await prisma.user.findMany()
|
|
}
|
|
```
|
|
|
|
4. Remove old instantiations
|
|
|
|
5. Validate with grep (should find only one instance):
|
|
|
|
```bash
|
|
grep -rn "new PrismaClient()" --include="*.ts" --include="*.js" . | wc -l
|
|
```
|
|
|
|
Expected: `1`
|
|
|
|
---
|
|
|
|
## Scenario 2: Next.js Application
|
|
|
|
**Setup:**
|
|
|
|
1. Create `lib/prisma.ts` with global singleton pattern
|
|
|
|
2. Import in Server Components:
|
|
|
|
```typescript
|
|
import { prisma } from '@/lib/prisma'
|
|
|
|
export default async function UsersPage() {
|
|
const users = await prisma.user.findMany()
|
|
return <UserList users={users} />
|
|
}
|
|
```
|
|
|
|
3. Import in Server Actions:
|
|
|
|
```typescript
|
|
'use server'
|
|
|
|
import { prisma } from '@/lib/prisma'
|
|
|
|
export async function createUser(formData: FormData) {
|
|
const email = formData.get('email') as string
|
|
return await prisma.user.create({ data: { email } })
|
|
}
|
|
```
|
|
|
|
4. Import in Route Handlers:
|
|
|
|
```typescript
|
|
import { NextResponse } from 'next/server'
|
|
import { prisma } from '@/lib/prisma'
|
|
|
|
export async function GET() {
|
|
const users = await prisma.user.findMany()
|
|
return NextResponse.json(users)
|
|
}
|
|
```
|
|
|
|
5. Set `connection_limit=1` in DATABASE_URL for Vercel:
|
|
|
|
```
|
|
DATABASE_URL="postgresql://user:pass@host:5432/db?connection_limit=1"
|
|
```
|
|
|
|
**Validation:**
|
|
|
|
- Hot reload shouldn't create new connections
|
|
- No P1017 errors in development
|
|
- Production deployments handle concurrent requests
|
|
|
|
---
|
|
|
|
## Scenario 3: Encountering P1017 Errors
|
|
|
|
**Symptoms:**
|
|
|
|
- "Can't reach database server" errors
|
|
- "Too many connections" in database logs
|
|
- Intermittent connection failures
|
|
- Error code: P1017
|
|
|
|
**Diagnosis:**
|
|
|
|
1. Grep codebase for `new PrismaClient()`:
|
|
|
|
```bash
|
|
grep -rn "new PrismaClient()" --include="*.ts" --include="*.js" .
|
|
```
|
|
|
|
2. Check count of instances:
|
|
|
|
```bash
|
|
grep -rn "new PrismaClient()" --include="*.ts" --include="*.js" . | wc -l
|
|
```
|
|
|
|
If > 1: Multiple instance problem
|
|
|
|
3. Review connection pool configuration:
|
|
|
|
```bash
|
|
grep -rn "connection_limit" .env* schema.prisma
|
|
```
|
|
|
|
If missing in serverless: Misconfiguration problem
|
|
|
|
**Fix:**
|
|
|
|
1. Implement singleton pattern (see Scenario 1)
|
|
|
|
2. Configure connection_limit for serverless:
|
|
|
|
**Development (.env.local):**
|
|
|
|
```
|
|
DATABASE_URL="postgresql://user:pass@host:5432/db?connection_limit=10"
|
|
```
|
|
|
|
**Production (Vercel):**
|
|
|
|
```
|
|
DATABASE_URL="postgresql://user:pass@host:5432/db?connection_limit=1"
|
|
```
|
|
|
|
3. Monitor connection count after deployment:
|
|
|
|
```sql
|
|
SELECT count(*) FROM pg_stat_activity WHERE datname = 'your_database';
|
|
```
|
|
|
|
Expected: Should stabilize at reasonable number (not growing)
|
|
|
|
---
|
|
|
|
## Scenario 4: Multiple Files Creating Clients
|
|
|
|
**Problem:** Different service files create their own clients
|
|
|
|
**Before:**
|
|
|
|
**`services/users.ts`:**
|
|
|
|
```typescript
|
|
import { PrismaClient } from '@prisma/client'
|
|
const prisma = new PrismaClient()
|
|
|
|
export async function getUsers() {
|
|
return await prisma.user.findMany()
|
|
}
|
|
```
|
|
|
|
**`services/posts.ts`:**
|
|
|
|
```typescript
|
|
import { PrismaClient } from '@prisma/client'
|
|
const prisma = new PrismaClient()
|
|
|
|
export async function getPosts() {
|
|
return await prisma.post.findMany()
|
|
}
|
|
```
|
|
|
|
**Problems:**
|
|
|
|
- Two separate connection pools
|
|
- Doubled memory usage
|
|
- Doubled connection count
|
|
- Multiplies with every service file
|
|
|
|
**After:**
|
|
|
|
**`lib/db.ts`:**
|
|
|
|
```typescript
|
|
import { PrismaClient } from '@prisma/client'
|
|
const prisma = new PrismaClient()
|
|
export default prisma
|
|
```
|
|
|
|
**`services/users.ts`:**
|
|
|
|
```typescript
|
|
import prisma from '@/lib/db'
|
|
|
|
export async function getUsers() {
|
|
return await prisma.user.findMany()
|
|
}
|
|
```
|
|
|
|
**`services/posts.ts`:**
|
|
|
|
```typescript
|
|
import prisma from '@/lib/db'
|
|
|
|
export async function getPosts() {
|
|
return await prisma.post.findMany()
|
|
}
|
|
```
|
|
|
|
**Result:**
|
|
|
|
- Single connection pool shared across services
|
|
- Reduced memory usage
|
|
- Stable connection count
|
|
|
|
---
|
|
|
|
## Connection Pool Configuration
|
|
|
|
The singleton pattern works with proper pool configuration:
|
|
|
|
**Default pool size:** 10 connections per PrismaClient
|
|
|
|
**Serverless (Vercel, Lambda):**
|
|
|
|
```
|
|
DATABASE_URL="postgresql://user:pass@host/db?connection_limit=1"
|
|
```
|
|
|
|
**Traditional servers:**
|
|
|
|
Calculate: `connection_limit = (num_instances * 2) + 1`
|
|
|
|
- 1 server = 3 connections
|
|
- 2 servers = 5 connections
|
|
- 4 servers = 9 connections
|
|
|
|
**Development:**
|
|
|
|
Default (10) is fine since only one developer instance runs.
|
|
|
|
**Example configuration per environment:**
|
|
|
|
```typescript
|
|
const connectionLimit = process.env.NODE_ENV === 'production'
|
|
? 1
|
|
: 10
|
|
|
|
export const prisma = new PrismaClient({
|
|
datasources: {
|
|
db: {
|
|
url: process.env.DATABASE_URL + `?connection_limit=${connectionLimit}`
|
|
}
|
|
}
|
|
})
|
|
```
|
|
|
|
---
|
|
|
|
## Why This Matters
|
|
|
|
Real-world impact from stress testing:
|
|
|
|
- **80% of agents** created multiple instances
|
|
- **100% of those** would fail in production under load
|
|
- **P1017 errors** in serverless after ~10 concurrent requests
|
|
- **Memory leaks** from abandoned connection pools
|
|
- **Database locked out** teams during testing
|
|
|
|
**The singleton pattern prevents all of these issues.**
|
|
|
|
Use this pattern **always**, even if your app is small. It becomes critical as you scale, and retrofitting is painful.
|