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 같은 전용 태스크 큐 도입도 고려해볼 만하다.