Trên serverless mỗi request có thể spin một function instance riêng. Nếu mỗi instance mở một TCP connection trực tiếp tới Postgres, lúc traffic cao bạn cạn max_connections (Postgres thường chỉ ~100) → lỗi 'too many connections'.
Hình dung: mỗi function là một người mới bốc máy gọi điện; database chỉ có vài đường dây — gọi đồng loạt là nghẽn.
Cách xử lý:
- Dùng connection pooler đứng giữa: PgBouncer, Neon pooled endpoint, Supabase Pooler, Prisma Accelerate. App nối tới pooler, pooler tái sử dụng số connection thật ít ỏi.
- Driver serverless dùng HTTP/WebSocket thay vì TCP dài hạn: vd @neondatabase/serverless, Drizzle Neon adapter — mỗi query là một HTTP call, không giữ socket.
// Drizzle + Neon serverless (HTTP, không giữ TCP)
import { neon } from '@neondatabase/serverless'
import { drizzle } from 'drizzle-orm/neon-http'
export const db = drizzle(neon(process.env.DATABASE_URL!))Lưu ý: Prisma với pooler cần ?pgbouncer=true (tắt prepared statements) và một URL DIRECT_URL riêng cho migration.
Đừng tạo new PrismaClient() mỗi request — cache global trong dev để tránh rò connection.
On serverless, each request may spin up a separate function instance. If every instance opens a direct TCP connection to Postgres, under high traffic you exhaust max_connections (Postgres often ~100) → 'too many connections' errors.
Picture it: each function is a new person grabbing a phone; the database has only a few lines — everyone calling at once jams it.
How to handle it:
- Put a connection pooler in the middle: PgBouncer, Neon pooled endpoint, Supabase Pooler, Prisma Accelerate. The app connects to the pooler, which reuses a small set of real connections.
- Use a serverless driver over HTTP/WebSocket instead of long-lived TCP: e.g. @neondatabase/serverless, Drizzle's Neon adapter — each query is an HTTP call, no held socket.
// Drizzle + Neon serverless (HTTP, no held TCP)
import { neon } from '@neondatabase/serverless'
import { drizzle } from 'drizzle-orm/neon-http'
export const db = drizzle(neon(process.env.DATABASE_URL!))Note: Prisma with a pooler needs ?pgbouncer=true (disables prepared statements) and a separate DIRECT_URL for migrations.
Don't create new PrismaClient() per request — cache it on a global in dev to avoid connection leaks.