FastAPI에서 비동기 DB 커넥션 풀 관리하기
문제 상황
사내 API 서버를 FastAPI로 리팩토링하면서 asyncpg를 도입했다. 로컬에서는 문제없었는데, 스테이징 배포 후 동시 요청이 50개 정도 넘어가면 too many connections 에러가 발생했다.
원인 파악
문제는 커넥션 풀 설정이었다. 기본 설정으로는 max_size=10이었고, 각 워커 프로세스마다 별도 풀을 생성하고 있었다. Gunicorn 워커 4개 × 풀 사이즈 10 = 40개의 커넥션만 가능한 상태였다.
# 문제가 있던 코드
async def get_pool():
return await asyncpg.create_pool(
dsn=DATABASE_URL,
min_size=5,
max_size=10
)
해결 방법
- 커넥션 풀을 애플리케이션 라이프사이클과 연결했다.
- 풀 사이즈를 워커 수와 트래픽을 고려해 조정했다.
- 커넥션 타임아웃을 명시적으로 설정했다.
from contextlib import asynccontextmanager
from fastapi import FastAPI
pool = None
@asynccontextmanager
async def lifespan(app: FastAPI):
global pool
pool = await asyncpg.create_pool(
dsn=DATABASE_URL,
min_size=10,
max_size=50,
command_timeout=60,
max_inactive_connection_lifetime=300
)
yield
await pool.close()
app = FastAPI(lifespan=lifespan)
async def get_connection():
async with pool.acquire() as conn:
yield conn
결과
동시 요청 200개까지 안정적으로 처리되는 것을 확인했다. 모니터링 결과 평균 커넥션 사용량은 30개 정도로 유지되고 있다.
추가로 max_inactive_connection_lifetime을 설정해서 유휴 커넥션이 자동으로 정리되도록 했다. DB 서버 부하도 줄어드는 효과가 있었다.