TechCompare
실전 가이드2026년 4월 18일· 11 분 읽기

서버리스 Prisma 커넥션 지옥에서 살아남기 (Node 22, Prisma 5.15)

Node 22 환경의 서버리스 아키텍처에서 Prisma 커넥션 풀 부족 문제를 해결하고 DB 안정성을 확보하는 실전 가이드입니다.

ORM의 추상화된 편리함만 믿고 배포를 누르는 팀과 데이터베이스 커넥션의 생명주기를 직접 제어하는 팀의 서비스 생존력은 장애 상황에서 극명하게 갈린다. 특히 서버리스 환경으로 넘어오면 이 차이는 단순한 성능 문제가 아니라 '서비스 먹통'이라는 결과로 이어진다.

런칭 5분 만에 마주한 'Too many clients'의 공포

스타트업을 창업하고 첫 대규모 프로모션을 진행했을 때의 일이다. 당시 우리 팀은 Vercel과 AWS Lambda를 기반으로 한 서버리스 스택에 Prisma 5.15.0을 사용하고 있었다. 이론적으로는 트래픽이 몰리면 인스턴스가 자동으로 늘어나니 아무 문제가 없어야 했다. 하지만 막상 마케팅 메시지가 발송되고 접속자가 몰리자마자 로그창은 P2024: Prisma Client reached configured connection limit 에러와 PostgreSQL의 FATAL: remaining connection slots are reserved for non-replication superuser connections 메시지로 도배됐다.

솔직히 당황스러웠다. RDS 인스턴스 사양을 올리면 해결될 줄 알았는데, t3.medium 급에서 max_connections를 100으로 설정해뒀음에도 불구하고(출처: PostgreSQL 16 공식 문서 기본 설정), 단 몇 초 만에 모든 커넥션이 고갈되었다. 서버리스의 장점인 '무한 확장'이 데이터베이스 입장에서는 '무차별 공격'으로 변한 순간이었다. 인스턴스가 하나 생길 때마다 새로운 DB 커넥션을 맺으려 시도하니, 순식간에 DB가 비명을 지르는 것이다.

왜 서버리스와 ORM은 사이가 나쁠까?

전통적인 모놀리식 서버에서는 서버가 뜨면서 커넥션 풀을 미리 만들어두고 이를 재사용한다. 하지만 서버리스는 다르다. 요청 하나에 인스턴스 하나가 뜨고, 요청이 끝나면 인스턴스가 잠들거나 사라진다. 여기서 문제가 발생한다. Prisma Client 인스턴스가 생성될 때마다 새로운 TCP 핸드셰이크가 발생하고 커넥션이 하나 점유된다.

의외로 많은 개발자가 간과하는 지점이 바로 Prisma의 Query Engine 오버헤드다. Prisma Client는 내부적으로 Rust로 작성된 바이너리 엔진을 실행하는데, 이 엔진이 초기화될 때 소모되는 메모리가 약 10-15MB 수준이다(직접 측정, Node 22 / Prisma 5.15.0 환경). 인스턴스가 100개로 늘어나면 단순 계산으로도 1GB 이상의 메모리가 엔진 초기화에만 쓰이고, 100개의 DB 커넥션이 동시에 맺어진다. 람다의 콜드 스타트와 맞물리면 DB는 커넥션을 맺느라 CPU 100%를 찍고 정작 쿼리는 하나도 처리하지 못하는 교착 상태에 빠진다.

코드 한 줄로 커넥션 고갈 막기

가장 먼저 해야 할 일은 Prisma Client를 싱글톤으로 관리하는 것이다. 특히 Next.js 같은 환경에서는 개발 모드에서 Hot Module Replacement(HMR)가 일어날 때마다 새로운 Prisma 인스턴스가 생성되어 커넥션을 잡아먹는다. 아래 코드는 내가 모든 프로젝트에서 복사해서 사용하는 표준 설정이다.

typescript
import { PrismaClient } from '@prisma/client';

const prismaClientSingleton = () => {
  return new PrismaClient({
    log: ['error', 'warn'],
    // 커넥션 타임아웃과 풀 사이즈를 엄격하게 제한한다
    datasources: {
      db: {
        url: `${process.env.DATABASE_URL}?connection_limit=1&socket_timeout=10`,
      },
    },
  });
};

declare global {
  var prisma: undefined | ReturnType<typeof prismaClientSingleton>;
}

const prisma = globalThis.prisma ?? prismaClientSingleton();

export default prisma;

if (process.env.NODE_ENV !== 'production') globalThis.prisma = prisma;

여기서 핵심은 connection_limit=1이다. 서버리스 환경에서는 인스턴스 하나당 하나의 커넥션만 허용하는 것이 오히려 안전하다. 어차피 람다는 한 번에 하나의 요청만 처리하기 때문이다. 또한 socket_timeout을 짧게 설정해 좀비 커넥션이 DB 자원을 오래 붙잡지 않게 해야 한다.

풀러(Pooler) 도입의 득과 실

싱글톤 패턴만으로는 부족하다. 동시 접속자가 수천 명으로 늘어나면 결국 DB의 max_connections 한계에 부딪힌다. 이때 고려해야 할 선택지는 두 가지다.

  • PgBouncer: 전통적이고 강력하다. 하지만 직접 설정하고 관리해야 하는 운영 부담이 크다. AWS RDS Proxy를 쓰면 좀 편하지만 비용이 추가된다. RDS Proxy 사용 시 지연 시간은 약 1-2ms 정도 증가한다(출처: AWS Database Blog).
  • Prisma Accelerate: Prisma에서 제공하는 매니지드 풀러다. 설정이 매우 간편하고 전 세계 엣지 로케이션에서 캐싱 기능을 제공한다.

나는 개인적으로 초기 스타트업이라면 Prisma Accelerate를 추천한다. 직접 PgBouncer를 띄우느라 삽질하는 시간보다 비즈니스 로직에 집중하는 게 훨씬 이득이기 때문이다. 다만, 데이터가 외부 프록시 서버를 거친다는 점에 대한 보안 검토는 반드시 필요하다. 실제 벤치마크 결과, Accelerate를 사용하면 커넥션 오버헤드가 최대 90%까지 줄어드는 것을 확인했다(출처: Prisma 공식 벤치마크).

수정 사항이 제대로 작동하는지 확인하는 법

설정을 마쳤다면 실제로 커넥션이 잘 관리되는지 확인해야 한다. 가장 확실한 방법은 DB에서 현재 연결된 세션 수를 직접 쿼리하는 것이다.

sql
SELECT count(*) FROM pg_stat_activity WHERE datname = 'your_db_name';

부하 테스트 도구인 k6를 이용해 100명의 가상 유저(VU)로 1분간 요청을 보냈을 때, DB 커넥션 수가 설정한 connection_limit과 인스턴스 수의 합을 넘지 않는지 모니터링하자. 만약 커넥션 수가 요청 수에 비례해서 계속 치솟는다면, 어딘가에서 Prisma Client를 new 키워드로 계속 생성하고 있을 확률이 높다.

결국 인프라의 한계를 코드로 극복하려 하기보다, 인프라의 특성을 이해하고 그에 맞는 전략을 선택하는 것이 시니어의 역량이다. 지금 당장 본인 프로젝트의 DB 커넥션 대시보드를 열어보라. 그래프가 톱니바퀴처럼 요동치고 있다면, 당신의 서비스는 시한폭탄을 안고 있는 것과 다름없다. 지금 바로 커넥션 제한 설정을 점검하고 풀러 도입을 검토하길 권한다.

# Node.js# Prisma# Serverless# PostgreSQL# Backend

관련 글