FastAPI 백그라운드 태스크에서 DB 세션 관리 문제 해결

문제 상황

사용자 요청 후 이메일 발송을 백그라운드로 처리하려고 FastAPI의 BackgroundTasks를 사용했다. 그런데 백그라운드 태스크 실행 시점에 DB 세션이 이미 닫혀있어 sqlalchemy.orm.exc.DetachedInstanceError가 발생했다.

@app.post("/users/")
async def create_user(
    user: UserCreate,
    background_tasks: BackgroundTasks,
    db: Session = Depends(get_db)
):
    db_user = User(**user.dict())
    db.add(db_user)
    db.commit()
    db.refresh(db_user)
    
    background_tasks.add_task(send_welcome_email, db_user)
    return db_user

원인 분석

FastAPI의 의존성 주입 시스템은 응답을 반환하는 시점에 컨텍스트를 정리한다. get_db로 주입된 세션도 이때 닫히는데, BackgroundTasks는 응답 후에 실행되기 때문에 세션이 없는 상태였다.

해결 방법

백그라운드 태스크에서 필요한 데이터만 추출해 전달하거나, 태스크 내부에서 새로운 세션을 생성하도록 수정했다.

def send_welcome_email(user_id: int, email: str):
    # 새로운 세션 생성
    db = SessionLocal()
    try:
        # 필요한 작업 수행
        user = db.query(User).filter(User.id == user_id).first()
        # 이메일 발송 로직
    finally:
        db.close()

@app.post("/users/")
async def create_user(
    user: UserCreate,
    background_tasks: BackgroundTasks,
    db: Session = Depends(get_db)
):
    db_user = User(**user.dict())
    db.add(db_user)
    db.commit()
    db.refresh(db_user)
    
    # 필요한 데이터만 전달
    background_tasks.add_task(
        send_welcome_email,
        db_user.id,
        db_user.email
    )
    return db_user

교훈

FastAPI의 의존성 주입은 편리하지만 라이프사이클을 명확히 이해해야 한다. 백그라운드 태스크처럼 요청 컨텍스트 밖에서 실행되는 코드는 별도의 리소스 관리가 필요하다. 무거운 작업이라면 Celery 같은 전용 태스크 큐 도입도 고려해볼 만하다.