Next.js로 개발하다 보면 이런 상황을 겪는다.
API 코드를 수정하고 저장한다. 브라우저에서 새로고침한다. 잘 된다. 몇 번 더 수정하고 저장한다. 갑자기 API가 먹통이 된다.
Error: Too many connections
콘솔에 에러가 뜨고, 모든 API 요청이 실패한다. 개발 서버를 재시작하면 다시 동작한다. 하지만 또 몇 번 저장하면 같은 일이 반복된다.
혼자 개발하고 있는데 연결이 부족하다니, 뭔가 이상하다.
문제의 원인: HMR이 연결을 쌓는다
Next.js는 개발 환경에서 HMR(Hot Module Reload) 을 사용한다. 파일을 저장하면 해당 모듈만 다시 로드해서 빠른 개발 경험을 제공한다.
문제는 Prisma Client다. 모듈이 다시 로드될 때마다 새로운 PrismaClient 인스턴스가 생성된다. 이전 인스턴스의 연결은 끊기지 않고 남아있다. 새 인스턴스가 또 연결을 맺는다. 이 과정이 반복되면 연결이 계속 쌓인다.
파일 저장 → HMR 발동 → 새 PrismaClient 생성 → 연결 5개 점유
파일 저장 → HMR 발동 → 새 PrismaClient 생성 → 연결 5개 추가 (총 10개)
파일 저장 → HMR 발동 → 새 PrismaClient 생성 → 연결 5개 추가 (총 15개)
...
→ 100개 초과 → Too many connections
PostgreSQL의 기본 최대 연결 수는 100개다. 파일을 20번만 저장해도 도달할 수 있다. 집중해서 코드 수정하다 보면 금방이다.
결과적으로 개발 흐름이 끊긴다. 코드 수정 → 확인 → 수정 사이클이 끊기고, 서버 재시작을 기다려야 한다. 몰입이 깨진다.
해결책: Global Singleton 패턴
해결 방법은 단순하다. PrismaClient 인스턴스를 전역 객체에 저장해서 재사용한다.
// lib/prisma.ts
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;
}
핵심은 globalThis다. Node.js에서 globalThis는 HMR이 발생해도 유지된다. 모듈이 다시 로드되어도 globalForPrisma.prisma에 이전 인스턴스가 남아있으면 그걸 재사용한다.
이제 파일을 아무리 저장해도 연결 수는 일정하게 유지된다.
연결 풀 설정 추가
Singleton만으로 끝이 아니다. 연결 풀 크기도 제어해야 한다.
const CONNECTION_POOL_SIZE = process.env.NODE_ENV === 'production' ? 10 : 5;
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
log: ['error', 'warn'],
datasourceUrl: process.env.DATABASE_URL
? `${process.env.DATABASE_URL}${process.env.DATABASE_URL.includes('?') ? '&' : '?'}connection_limit=${CONNECTION_POOL_SIZE}&pool_timeout=30`
: undefined,
});
| 파라미터 | 값 | 설명 |
|---|---|---|
connection_limit |
개발 5 / 프로덕션 10 | 동시 연결 수 제한 |
pool_timeout |
30초 | 연결 대기 타임아웃 |
log |
error, warn | 필요한 로그만 출력 |
개발 환경에서는 5개면 충분하다. 프로덕션에서는 동시 요청이 많으니 10개로 늘린다.
Supabase를 쓴다면
Supabase는 이미 pgbouncer를 통해 연결 풀링을 제공한다. Transaction Pooler(port 6543)를 사용하면 클라이언트 측 연결 풀과 서버 측 연결 풀이 이중으로 작동한다.
# Transaction Pooler (일반 쿼리용)
DATABASE_URL="postgresql://...@pooler.supabase.com:6543/postgres?pgbouncer=true"
# Direct Connection (마이그레이션용)
DIRECT_URL="postgresql://...@db.xxx.supabase.co:5432/postgres"
이 경우 connection_limit을 너무 높게 잡으면 오히려 역효과다. pgbouncer가 이미 연결을 관리하고 있으니, 클라이언트 측에서는 적당히 제한하는 게 낫다.
- Global Singleton: HMR에서 인스턴스 재사용
- 연결 풀 제한: 환경별로 적정 수준 유지
- 타임아웃 설정: 연결 점유 시간 제한
- Supabase: pgbouncer와 조합 시 클라이언트 풀은 작게
개발 중 "Too many connections" 에러가 나면 십중팔구 Singleton 패턴이 빠져있다. 간단한 설정이지만, 없으면 개발 흐름이 끊긴다.
프런트엔드 엔지니어, QA 엔지니어 그리고 디자이너를 위한
" ALL IN ONE " QA 서비스
https://pixeldiff.turtle-tail.com
'FrontEnd > React' 카테고리의 다른 글
| React 조건부 렌더링 최적화: visibility 기반 캐싱으로 모드 전환 개선하기 (0) | 2026.02.26 |
|---|---|
| React Query 낙관적 업데이트, 두 가지 패턴 (0) | 2026.02.17 |
| NextAuth.js 세션 전략: DB에서 JWT로 전환하여 400ms → 5ms 달성하기 (0) | 2026.02.03 |
| Next.js로만 백엔드, 프론트엔드 구축하기 (0) | 2026.01.24 |
| Zustand로 Undo/Redo 구현하기 (0) | 2026.01.13 |
