FastAPI에서 비동기 DB 쿼리 최적화하기

문제 상황

사용자가 늘면서 API 응답 시간이 평균 800ms에서 2초 이상으로 늘어났다. FastAPI를 비동기로 사용하고 있었지만, DB 쿼리 부분에서 병목이 발생하고 있었다.

기존 코드는 SQLAlchemy 1.4의 동기 엔진을 사용하고 있었다.

# 기존 코드
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(bind=engine)

@app.get("/users/{user_id}")
async def get_user(user_id: int):
    db = SessionLocal()
    user = db.query(User).filter(User.id == user_id).first()
    return user

비동기 엔드포인트인데 DB 쿼리는 동기로 실행되고 있었던 것이다.

해결 방법

SQLAlchemy의 asyncio 확장을 사용하도록 변경했다.

from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker

engine = create_async_engine(
    DATABASE_URL.replace("postgresql://", "postgresql+asyncpg://"),
    pool_size=20,
    max_overflow=10
)

AsyncSessionLocal = sessionmaker(
    engine, class_=AsyncSession, expire_on_commit=False
)

async def get_db():
    async with AsyncSessionLocal() as session:
        yield session

@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

추가로 connection pool 설정도 조정했다. 기본값인 5개로는 동시 요청을 처리하기에 부족했다.

결과

  • 평균 응답 시간: 2초 → 300ms
  • P99 응답 시간: 5초 → 800ms
  • 동시 처리 가능 요청 수 증가

비동기를 제대로 활용하지 못하면 오히려 오버헤드만 생긴다는 걸 다시 한번 확인했다. asyncpg 드라이버로 변경한 것도 성능 향상에 도움이 됐다.