Initial commit
This commit is contained in:
310
skills/creating-client-singletons/references/common-scenarios.md
Normal file
310
skills/creating-client-singletons/references/common-scenarios.md
Normal file
@@ -0,0 +1,310 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user