Initial commit
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
# Serverless Pattern
|
||||
|
||||
Serverless environments require special handling due to cold starts, connection pooling, and function lifecycle constraints.
|
||||
|
||||
## Next.js App Router (Vercel)
|
||||
|
||||
**File: `lib/prisma.ts`**
|
||||
|
||||
```typescript
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
|
||||
const globalForPrisma = globalThis as unknown as {
|
||||
prisma: PrismaClient | undefined
|
||||
}
|
||||
|
||||
export const prisma = globalForPrisma.prisma ?? new PrismaClient({
|
||||
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
|
||||
})
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
|
||||
```
|
||||
|
||||
**Environment Configuration (.env):**
|
||||
|
||||
```
|
||||
DATABASE_URL="postgresql://user:pass@host:5432/db?connection_limit=1&pool_timeout=10"
|
||||
```
|
||||
|
||||
**Why connection_limit=1:**
|
||||
|
||||
- Each serverless function instance gets ONE connection
|
||||
- Multiple function instances = multiple connections
|
||||
- Prevents pool exhaustion with many concurrent requests
|
||||
- Vercel scales to hundreds of instances automatically
|
||||
|
||||
**Usage in Server Components:**
|
||||
|
||||
```typescript
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export default async function UsersPage() {
|
||||
const users = await prisma.user.findMany()
|
||||
return <UserList users={users} />
|
||||
}
|
||||
```
|
||||
|
||||
**Usage 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 }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
**Usage 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)
|
||||
}
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
|
||||
- Never create PrismaClient in component files
|
||||
- Import singleton from `lib/prisma.ts`
|
||||
- Global pattern survives hot reload
|
||||
- Connection limit prevents pool exhaustion
|
||||
|
||||
---
|
||||
|
||||
## AWS Lambda
|
||||
|
||||
**File: `lib/db.ts`**
|
||||
|
||||
```typescript
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
|
||||
let prisma: PrismaClient
|
||||
|
||||
if (!global.prisma) {
|
||||
global.prisma = new PrismaClient({
|
||||
log: ['error', 'warn']
|
||||
})
|
||||
}
|
||||
|
||||
prisma = global.prisma
|
||||
|
||||
export default prisma
|
||||
```
|
||||
|
||||
**Lambda Handler:**
|
||||
|
||||
```typescript
|
||||
import prisma from './lib/db'
|
||||
|
||||
export async function handler(event: any) {
|
||||
const users = await prisma.user.findMany()
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(users)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Environment Variables (Lambda):**
|
||||
|
||||
```
|
||||
DATABASE_URL=postgresql://user:pass@host:5432/db?connection_limit=1&pool_timeout=10&connect_timeout=10
|
||||
```
|
||||
|
||||
**Lambda-Specific Considerations:**
|
||||
|
||||
- Lambda reuses container for warm starts
|
||||
- Global singleton survives across invocations
|
||||
- First invocation creates client (cold start)
|
||||
- Subsequent invocations reuse client (warm starts)
|
||||
- No need to disconnect (Lambda freezes container)
|
||||
|
||||
---
|
||||
|
||||
## Connection Pool Calculation for Serverless
|
||||
|
||||
**Formula:**
|
||||
|
||||
```
|
||||
max_connections = (max_concurrent_functions * connection_limit) + buffer
|
||||
```
|
||||
|
||||
**Example (Vercel):**
|
||||
|
||||
- Max concurrent functions: 100
|
||||
- Connection limit per function: 1
|
||||
- Buffer: 10
|
||||
|
||||
**Result:** Need 110 database connections
|
||||
|
||||
**Recommended DATABASE_URL for Vercel:**
|
||||
|
||||
```
|
||||
postgresql://user:pass@host:5432/db?connection_limit=1&pool_timeout=10
|
||||
```
|
||||
|
||||
**Why pool_timeout=10:**
|
||||
|
||||
- Prevents long waits for connections
|
||||
- Fails fast if pool exhausted
|
||||
- User gets error instead of timeout
|
||||
|
||||
---
|
||||
|
||||
## Anti-Pattern: Multiple Files Creating Clients
|
||||
|
||||
**WRONG - Each file creates its own:**
|
||||
|
||||
**`app/api/users/route.ts`:**
|
||||
|
||||
```typescript
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
export async function GET() {
|
||||
return Response.json(await prisma.user.findMany())
|
||||
}
|
||||
```
|
||||
|
||||
**`app/api/posts/route.ts`:**
|
||||
|
||||
```typescript
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
export async function GET() {
|
||||
return Response.json(await prisma.post.findMany())
|
||||
}
|
||||
```
|
||||
|
||||
**Problems in Serverless:**
|
||||
|
||||
- Each route = separate client = separate pool
|
||||
- 2 routes × 50 function instances × 10 connections = 1000 connections!
|
||||
- Database exhausted under load
|
||||
- P1017 errors inevitable
|
||||
|
||||
**Fix - Central singleton:**
|
||||
|
||||
**`lib/prisma.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
|
||||
```
|
||||
|
||||
**`app/api/users/route.ts`:**
|
||||
|
||||
```typescript
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function GET() {
|
||||
return Response.json(await prisma.user.findMany())
|
||||
}
|
||||
```
|
||||
|
||||
**`app/api/posts/route.ts`:**
|
||||
|
||||
```typescript
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function GET() {
|
||||
return Response.json(await prisma.post.findMany())
|
||||
}
|
||||
```
|
||||
|
||||
**Result:**
|
||||
|
||||
- 50 function instances × 1 connection = 50 connections
|
||||
- Sustainable and scalable
|
||||
- No P1017 errors
|
||||
Reference in New Issue
Block a user