요즘 Next.js API routes나 AWS Lambda 같은 서버리스 아키텍처를 도입하는 프로젝트가 정말 많잖아. 그런데 서비스가 조금만 활성화되면 데이터베이스(특히 PostgreSQL 같은 RDBMS)가 커넥션 초과로 뻗어버리는 장애를 자주 겪게 돼. 이건 트래픽이 엄청나게 많아서라기보다, 서버리스의 동작 방식과 전통적인 DB 커넥션 관리 방식이 충돌해서 발생하는 문제거든.

database server technology

1. 서버리스가 DB 커넥션을 고갈시키는 이유

전통적인 백엔드 애플리케이션은 서버가 항상 켜져 있으니까 HikariCP 같은 커넥션 풀을 만들어두고 평화롭게 재사용하거든. 내가 12년 동안 백엔드 개발을 하면서 가장 많이 본 구조도 이거야. 하지만 서버리스 환경은 완전히 달라. 요청이 들어올 때마다 독립적인 가상 컨테이너(샌드박스)가 번쩍 켜졌다가 꺼지거든. 만약 AWS Lambda 인스턴스 50개가 동시에 뜨면, 각각의 인스턴스가 DB 커넥션을 최소 1개에서 많게는 10개씩 맺으려고 시도해. 순식간에 수백 개의 TCP 커넥션이 생성되면서 RDBMS의 max_connections 한계를 가뿐히 넘겨버리는 거지. 인스턴스가 작업을 마치고 'Warm' 상태로 대기할 때도 커넥션을 꽉 잡고 있어서 정작 새로 뜨는 인스턴스는 DB에 붙지도 못하는 불상사가 생겨.

2. 외부 커넥션 풀러(PgBouncer, RDS Proxy) 도입하기

이 문제를 해결하는 가장 확실한 방법은 서버리스 함수와 DB 사이에 미들웨어 역할을 하는 **커넥션 풀러(Connection Pooler)**를 두는 거야. AWS 환경이라면 RDS Proxy를, 자체 인프라나 클라우드 VM을 쓴다면 PgBouncer를 적극 추천해. 이 친구들은 수천 개의 클라이언트 커넥션을 대신 받아주고, 실제 DB와의 물리 커넥션은 소수로 유지하면서 효율적으로 나눠서 써주거든. 특히 PgBouncer를 쓸 때는 **트랜잭션 모드(Transaction Mode)**를 사용하는 게 좋아. 쿼리가 실행되는 순간에만 커넥션을 할당하고 끝나면 즉시 풀에 반환하니까 커넥션 효율이 극대화되지. 다만 이 모드에서는 Prepared Statement나 세션 상태를 바꾸는 명령이 오작동할 수 있으니 드라이버 설정을 꼭 확인해야 해.

server programming code

3. 클라이언트 코드 최적화와 싱글톤 패턴

인프라를 고치는 것 외에 애플리케이션 코드에서도 조치를 취해야 해. Next.js나 Node.js 환경에서 DB 클라이언트 객체를 매번 새로 생성하면 안 되거든. 개발 단계에서 핫 리로딩이 일어날 때마다 커넥션이 무한 증식하는 경험을 해봤을 거야. 이를 막으려면 전역 변수를 활용한 싱글톤(Singleton) 패턴으로 DB 클라이언트를 단 한 번만 생성하도록 제한해야 해. 그리고 서버리스 환경에서는 클라이언트의 최대 커넥션 수(max pool size)를 과감하게 1 또는 아주 작은 수로 제한하는 게 정석이야. 어차피 하나의 서버리스 인스턴스는 한 번에 하나의 요청만 처리하니까 커넥션이 많이 필요 없거든. 추가로 idleTimeoutMillis 설정을 짧게 잡아서 안 쓰는 커넥션은 빠르게 정리되도록 세팅해 봐.

💡 핵심 정리

  • 서버리스의 특징: 인스턴스 급증으로 인한 DB 커넥션 고갈(Connection Exhaustion) 위험이 매우 큼.
  • 해결책 1: PgBouncer나 RDS Proxy 같은 전용 커넥션 풀러를 연동하고 트랜잭션 모드를 활용할 것.
  • 해결책 2: 애플리케이션 코드에서 DB 클라이언트를 싱글톤으로 구현하고, 인스턴스당 커넥션 수를 최소(1~2개)로 제한할 것. 서버리스는 확장성이 엄청난 장점이지만, RDBMS처럼 연결을 유지해야 하는 전통적인 기술과는 궁합이 안 맞는 면이 있어. 내가 알려준 풀러 설정과 클라이언트 제한을 프로젝트에 적용해 보면 DB가 비명을 지르는 일은 없을 거야. 실무에서 이 구조를 직접 설계해 보면 면접에서도 아주 강력한 무기가 될 테니 꼭 직접 구축해 봐!