생산성

Fly.io 무료 플랜 Postgres 연결 수 초과, PgBouncer로 해결한 1인 개발자 트러블슈팅

Fly.io 무료 플랜 Postgres 연결 수 초과, PgBouncer로 해결한 1인 개발자 트러블슈팅

배포는 됐어요. 그런데 앱이 안 뜨죠.

로그를 열었더니 too many connections for role 에러. 트래픽은 하루 수십 명 수준인데, Postgres가 연결 수 초과로 죽어 있어요. 1인 개발자 입장에서 이 상황은 꽤 당혹스러워요. Fly.io 무료 플랜에서 Postgres를 쓰는 개발자라면 2026년 현재도 이 함정에 빠지는 경우가 많거든요.

핵심 요약

  • Fly.io 무료 플랜의 Postgres 인스턴스는 기본 최대 동시 연결 수가 25개로 제한되며, Node.js나 Python 기반 앱은 기본 커넥션 풀 설정만으로도 이 한도를 쉽게 초과해요.
  • PgBouncer는 여러 앱 연결을 하나의 Postgres 연결로 묶어주는 커넥션 풀러로, Fly.io 커뮤니티에 따르면 설정 후 실제 연결 수를 90% 이상 줄인 사례가 보고됐어요.
  • Transaction 모드로 PgBouncer를 구성하면 연결 수를 가장 효과적으로 줄일 수 있지만, prepared statement를 못 쓰는 제약이 생겨요.
  • 1인 개발자에게 이 트러블슈팅은 단순한 에러 해결이 아니라, 앱 아키텍처를 처음부터 다시 생각하게 만드는 계기예요.

Fly.io 무료 Postgres, 연결이 이렇게 빨리 차는 이유

Fly.io의 Managed Postgres는 1인 개발자에게 매력적인 선택이에요. 클릭 몇 번으로 Postgres를 띄울 수 있고, 무료 플랜에서 시작해서 나중에 스케일업하면 되니까요.

문제는 구조에 있어요.

Fly.io Postgres는 내부적으로 PostgreSQL 프로세스가 직접 연결을 받아요. PostgreSQL은 연결 하나당 프로세스 하나를 생성하는 방식이에요. RAM이 256MB인 무료 인스턴스에서 max_connections는 기본값으로 25~100 사이로 설정되는데, 실제로는 25개 안팎에서 벽이 생기는 경우가 많아요.

그런데 Node.js의 pg 라이브러리는 기본 풀 크기가 10이에요. FastAPI + SQLAlchemy 조합이라면 기본 pool_size=5max_overflow=10이라 최대 15개까지 열릴 수 있어요. 앱 인스턴스가 서너 개만 떠도 연결이 꽉 차요.

Fly.io 공식 커뮤니티 포럼에서도 이 주제는 반복적으로 등장해요. 대부분 “트래픽은 적은데 연결 초과가 나냐"는 질문이에요. 트래픽 문제가 아니라 연결 생성 방식 자체의 문제거든요. 앱이 idle 상태에서도 연결을 붙잡고 있기 때문이에요.


PgBouncer가 하는 일: 연결 대리인

PgBouncer는 앱과 Postgres 사이에 앉아서 연결을 중계해줘요. 단순하게 설명하면 이래요:

앱 → PgBouncer → PostgreSQL

앱 쪽에서 연결 100개가 와도, PgBouncer가 Postgres에는 실제로 5개만 열어서 돌려막기 해요. HashCoder의 분석에 따르면 이 방식으로 동일 트래픽 대비 처리량을 세 배까지 높인 사례가 있어요.

PgBouncer에는 세 가지 풀링 모드가 있어요.

Session 모드 vs Transaction 모드 vs Statement 모드

항목Session 모드Transaction 모드Statement 모드
연결 공유 시점세션 종료 시트랜잭션 종료 시쿼리 종료 시
연결 절약 효과낮음높음매우 높음 (불안정)
Prepared Statement가능❌ 불가❌ 불가
SET 명령 유지가능제한적불가
1인 개발자 추천보조용기본 선택비추천

Transaction 모드가 핵심이에요. 연결을 트랜잭션이 끝나는 즉시 풀에 반환하기 때문에 실제 Postgres 연결 수를 극적으로 줄일 수 있어요.

단, prepared statement를 못 써요. SQLAlchemy에서 use_batch_mode=True 설정이 있거나, Prisma 같은 ORM이 내부적으로 prepared statement를 쓴다면 에러가 날 수 있어요. 반드시 확인하세요.

Fly.io에서 PgBouncer 붙이는 현실적인 방법

Fly.io에서 PgBouncer를 쓰는 방법은 크게 두 가지예요.

방법 A: Fly.io Postgres 앱 안에서 pgbouncer 프로세스를 직접 띄우기 (fly.toml에 프로세스 추가)

방법 B: 별도의 PgBouncer 전용 Fly 앱을 띄우고, DATABASE_URL을 그쪽으로 연결

Fly.io 커뮤니티 포럼에서 가장 많이 권장되는 방식은 방법 A예요. 같은 앱 내에서 실행하면 네트워크 홉이 줄고, 무료 플랜에서 추가 앱을 띄울 필요도 없어요.

pgbouncer.ini 핵심 설정은 이래요:

[databases]
mydb = host=localhost port=5432 dbname=mydb

[pgbouncer]
pool_mode = transaction
max_client_conn = 100
default_pool_size = 5
listen_port = 6432

max_client_conn은 앱이 PgBouncer에 열 수 있는 최대 연결 수고, default_pool_size가 실제 Postgres로 가는 연결 수예요. 이 두 값의 차이가 PgBouncer의 마법이에요.


실제로 어떤 시나리오에서 이게 필요한가

시나리오 1 - Next.js + Serverless 함수 조합

Serverless 환경은 함수 호출마다 새 연결을 열 수 있어요. Next.js API route에 동시 요청 20개만 들어와도 연결이 20개 생겨요. PgBouncer Transaction 모드면 이걸 5개 이내로 눌러줘요.

시나리오 2 - FastAPI + SQLAlchemy

SQLAlchemy의 NullPool을 쓰거나 pool_size를 1~2로 낮추는 방법도 있어요. 하지만 이건 성능을 포기하는 방식이에요. PgBouncer를 앞에 두면 SQLAlchemy는 여전히 커넥션 풀을 유지하면서, Postgres로 가는 실제 연결만 줄어들어요.

시나리오 3 - 여러 앱이 같은 DB를 쓸 때

Fly.io에서 여러 앱이 하나의 Postgres를 바라볼 때, 각 앱의 DATABASE_URL을 PgBouncer 포트(6432)로 바꾸면 돼요. Postgres 입장에서는 연결이 PgBouncer 하나뿐인 것처럼 보여요.


1인 개발자가 지금 당장 해야 할 것

트러블슈팅 순서를 정리하면 이래요.

  1. 현재 연결 수 확인: SELECT count(*) FROM pg_stat_activity; 로 현재 연결 상태 파악
  2. 앱 사이드 풀 설정 점검: 각 라이브러리의 기본 풀 크기 확인 (Node pg: 10, SQLAlchemy: 5+10)
  3. PgBouncer 추가 결정: 연결 수가 max_connections의 70%를 넘으면 추가 고려
  4. Transaction 모드로 시작: prepared statement 이슈 없으면 Transaction 모드가 가장 효과적
  5. SHOW POOLS; 로 모니터링: PgBouncer에 접속해서 SHOW POOLS; 치면 실시간 상태 확인 가능

앞으로 Fly.io 무료 플랜 생태계는 더 많은 1인 개발자들이 진입하면서, 이 연결 수 문제는 더 자주 등장할 거예요. Fly.io 측에서도 Managed Postgres에 내장 PgBouncer 옵션을 제공하는 방향을 커뮤니티에서 요청하고 있는 상태고요.


마무리: 연결 수 문제는 구조 문제예요

  • Fly.io 무료 플랜 Postgres는 연결 수 한도가 낮고, 앱 사이드 기본 설정만으로도 쉽게 초과해요
  • PgBouncer는 앱 연결을 Postgres 연결과 분리해서 실제 DB 연결 수를 90% 이상 줄일 수 있어요
  • Transaction 모드가 가장 효과적이지만, prepared statement 비호환성은 반드시 테스트해야 해요
  • 1인 개발자 트러블슈팅의 핵심은 에러 메시지보다 연결 생성 구조를 이해하는 데 있어요

배포 후 에러가 났을 때, 로그의 too many connections는 사실 앱이 아니라 아키텍처가 보내는 신호예요. PgBouncer 설정 한 번으로 해결되는 문제인데, 모르면 유료 플랜으로 업그레이드하거나 앱 자체를 뜯어고치게 되죠.

지금 Fly.io Postgres를 쓰고 있다면, pg_stat_activity 쿼리 한 번만 쳐보세요. 연결이 몇 개나 열려 있나요?

참고자료

  1. Fly Managed Postgres and database connection limit using the PG Bouncer - Phoenix - Fly.io
  2. The Developer’s Guide to Taming PostgreSQL: How We Tripled Throughput with PgBouncer | by HashCoder

Photo by Alex Gagareen on Unsplash