FastAPI에서 비동기 DB 커넥션 풀 관리하기
문제 상황
기존 Django 기반 API를 FastAPI로 전환하는 프로젝트를 진행 중이다. 동기 방식의 DB 쿼리를 비동기로 변경하면서 커넥션 풀 관리가 예상보다 까다로웠다.
초기에는 각 요청마다 DB 커넥션을 생성했더니 부하 테스트에서 커넥션 고갈 문제가 발생했다.
해결 방법
SQLAlchemy 1.4의 async 엔진을 사용해 커넥션 풀을 구성했다.
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
engine = create_async_engine(
"postgresql+asyncpg://user:pass@localhost/db",
pool_size=20,
max_overflow=10,
pool_pre_ping=True
)
AsyncSessionLocal = sessionmaker(
engine, class_=AsyncSession, expire_on_commit=False
)
async def get_db():
async with AsyncSessionLocal() as session:
yield session
FastAPI의 Dependency Injection을 활용해 각 엔드포인트에서 세션을 주입받도록 구성했다.
@app.get("/users/{user_id}")
async def get_user(user_id: int, db: AsyncSession = Depends(get_db)):
result = await db.execute(select(User).filter(User.id == user_id))
user = result.scalar_one_or_none()
return user
주의사항
pool_pre_ping=True 설정이 중요했다. DB 재시작 시 stale 커넥션을 자동으로 감지해 재연결한다.
contextmanager를 사용할 때 expire_on_commit=False 옵션을 주지 않으면 커밋 후 객체 속성 접근 시 추가 쿼리가 발생하는 문제가 있었다.
기존 Django ORM에 익숙한 팀원들을 위해 간단한 마이그레이션 가이드도 작성했다. asyncpg 드라이버가 psycopg2보다 성능이 좋다는 벤치마크 결과도 함께 공유했다.