troubleshooting2025-04-15·9 min·121/348

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 exceeded

Analysis

Vercel 서버리스 환경에서 Prisma 연결 문제가 발생하는 원인:

  1. Cold Start: 서버리스 함수가 시작될 때마다 새 연결 생성
  2. 연결 풀 부족: 기본 연결 풀 크기가 작음
  3. 연결 누수: 연결이 올바르게 종료되지 않음
  4. 네트워크 지연: 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

  1. 싱글톤 패턴: Prisma 클라이언트를 싱글톤으로 사용하면 연결을 효율적으로 관리할 수 있습니다.
  2. 연결 풀 설정: 애플리케이션의 부하에 맞게 연결 풀 크기를 조정해야 합니다.
  3. 에러 핸들링: 연결 에러에 대한 적절한 핸들링과 재시도 로직이 필요합니다.
  4. 모니터링: 연결 상태를 지속적으로 모니터링하여 문제를 조기에 발견해야 합니다.
  5. 환경 변수: 데이터베이스 연결 정보는 환경 변수로 관리해야 합니다.

This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.