Vercel에서 Prisma DB 연결 문제
Vercel 환경에서 Prisma를 사용할 때 발생하는 데이터베이스 연결 문제를 해결하는 방법을 알아봅니다.
Vercel에서 Prisma DB 연결 문제
Introduction
Vercel에서 Prisma를 사용할 때 데이터베이스 연결 문제가 자주 발생합니다. 서버리스 환경의 특성상 연결 풀링, 타임아웃, 연결 한도 등의 문제가 발생할 수 있습니다. 이 글에서는 Prisma DB 연결 문제를 진단하고 해결하는 방법을 알아보겠습니다.
Environment
- Vercel 프로젝트 (Next.js 14+)
- Prisma ORM 5+
- PostgreSQL (Supabase, Neon 등)
- Node.js 18+
Problem
Vercel에서 Prisma 연결 시 발생하는 일반적인 에러들:
# 연결 풀 에러
Error: Can't reach database server at `host:5432`
Please make sure your database server is running at `host:5432`
# 연결 한도 초과
Error: Too many database connections
Connection limit reached
# 타임아웃 에러
Error: Database connection timed out
Timeout of 10000ms exceededAnalysis
Vercel 서버리스 환경에서 Prisma 연결 문제가 발생하는 원인:
- Cold Start: 서버리스 함수가 시작될 때마다 새 연결 생성
- 연결 풀 부족: 기본 연결 풀 크기가 작음
- 연결 누수: 연결이 올바르게 종료되지 않음
- 네트워크 지연: Vercel 리전과 데이터베이스 리전 간 지연
Solution
1. Prisma 클라이언트 싱글톤 패턴
// lib/prisma.js
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis;
export const prisma = globalForPrisma.prisma ?? new PrismaClient({
log: process.env.NODE_ENV === 'development' ? ['query'] : [],
datasources: {
db: {
url: process.env.DATABASE_URL
}
}
});
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma;
}2. 연결 풀 설정
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
// 연결 풀 설정
directUrl = env("DIRECT_URL")
}
// 환경 변수
// DATABASE_URL="postgresql://user:pass@host:5432/db?connection_limit=20&pool_timeout=20"
// DIRECT_URL="postgresql://user:pass@host:5432/db"3. 연결 테스트 스크립트
// scripts/test-db-connection.js
const { PrismaClient } = require('@prisma/client');
async function testConnection() {
const prisma = new PrismaClient({
datasources: {
db: {
url: process.env.DATABASE_URL
}
}
});
try {
// 연결 테스트
await prisma.$connect();
console.log('Database connected successfully');
// 쿼리 테스트
const result = await prisma.$queryRaw`SELECT 1`;
console.log('Query executed:', result);
// 연결 상태 확인
const stats = await prisma.$metrics.json();
console.log('Connection stats:', stats);
} catch (error) {
console.error('Database connection failed:', error.message);
process.exit(1);
} finally {
await prisma.$disconnect();
}
}
testConnection();4. 연결 에러 핸들링
// lib/prisma-with-retry.js
import { PrismaClient } from '@prisma/client';
const MAX_RETRIES = 3;
const RETRY_DELAY = 1000;
class PrismaService {
constructor() {
this.prisma = null;
this.retryCount = 0;
}
async connect() {
if (this.prisma) {
return this.prisma;
}
try {
this.prisma = new PrismaClient({
datasources: {
db: {
url: process.env.DATABASE_URL
}
}
});
await this.prisma.$connect();
this.retryCount = 0;
return this.prisma;
} catch (error) {
this.retryCount++;
if (this.retryCount < MAX_RETRIES) {
console.log(`Retry ${this.retryCount}/${MAX_RETRIES}`);
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY));
return this.connect();
}
throw error;
}
}
async disconnect() {
if (this.prisma) {
await this.prisma.$disconnect();
this.prisma = null;
}
}
}
export const prismaService = new PrismaService();5. 서버리스 환경 최적화
// app/api/users/route.js
import { NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
export async function GET() {
try {
const users = await prisma.user.findMany({
take: 10,
orderBy: { createdAt: 'desc' }
});
return NextResponse.json(users);
} catch (error) {
console.error('Database query failed:', error);
// 연결 에러인 경우 재시도
if (error.code === 'P1001' || error.code === 'P1017') {
// 연결 풀 초기화
await prisma.$disconnect();
// 재시도 로직
return NextResponse.json(
{ error: 'Database temporarily unavailable' },
{ status: 503 }
);
}
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}6. 연결 모니터링
// lib/prisma-monitor.js
import { prisma } from './prisma';
export function setupPrismaMonitoring() {
// 쿼리 로깅
prisma.$on('query', (e) => {
console.log('Query: ' + e.query);
console.log('Duration: ' + e.duration + 'ms');
});
// 에러 로깅
prisma.$on('error', (e) => {
console.error('Prisma Error:', e.message);
});
// 정보 로깅
prisma.$on('info', (e) => {
console.log('Prisma Info:', e.message);
});
// 경고 로깅
prisma.$on('warn', (e) => {
console.warn('Prisma Warning:', e.message);
});
}7. 환경 변수 설정
# .env.local
DATABASE_URL="postgresql://user:password@host:5432/dbname?connection_limit=20&pool_timeout=30"
DIRECT_URL="postgresql://user:password@host:5432/dbname"Lessons Learned
- 싱글톤 패턴: Prisma 클라이언트를 싱글톤으로 사용하면 연결을 효율적으로 관리할 수 있습니다.
- 연결 풀 설정: 애플리케이션의 부하에 맞게 연결 풀 크기를 조정해야 합니다.
- 에러 핸들링: 연결 에러에 대한 적절한 핸들링과 재시도 로직이 필요합니다.
- 모니터링: 연결 상태를 지속적으로 모니터링하여 문제를 조기에 발견해야 합니다.
- 환경 변수: 데이터베이스 연결 정보는 환경 변수로 관리해야 합니다.
This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.