티스토리 뷰

 

서버 개발을 하다 보면 일단 응답을 내보내서 화면은 동작시키고, 서버 백그라운드에서 작업을 하도록 만들고 싶은 경우가 있다. 이런 작업을 보통 비동기 작업이라고 하는데, 스프링(Spring)에서는 @Async가 주로 쓰인다   스프링 부트에서 초간단 멀티쓰레드 구현하기 : @Async

 

FastAPI 기반으로 개발하면서 이런 상황에서 어떤 걸 쓸까 고민하며 찾아보니, 주로 asyncio BackgroundTasks라는 도구를 사용하고 있었다. 하지만 이 둘은 동작 방식과 시점이 크게 다르기 때문에, 각각 어떤 특징이 있고 주의할 점은 무엇인지 정리해 보려고 한다.

 

1. asyncio

파이썬에서 기본 제공하는 라이브러리로, 가장 핵심적인 비동기 처리 방식이다. 특징이 있다면 응답이 나가는 걸 대기하지 않고 즉시 동작한다는 점이다.

import asyncio
from fastapi import FastAPI

app = FastAPI()

async def some_library_task():
    await asyncio.sleep(5)  # 비동기 작업 시뮬레이션
    print("Task Complete!")

@app.get("/trigger")
async def trigger_task():
    # 호출 즉시 작업이 루프에 등록됨 (응답 대기 안 함)
    asyncio.create_task(some_library_task())
    return {"message": "Task triggered immediately"}

코드에서 보듯 asyncio.create_task()를 사용하는 순간, 해당 코루틴은 즉시 이벤트 루프(Event Loop)에 스케줄링된다. asyncio의 장점에 대해 정리해 보면 다음과 같다.

 

비차단(Non-blocking) 실행: await를 만나기 전까지는 메인 흐름을 방해하지 않고 작업을 던져놓는다. API의 return을 기다릴 필요 없이 백그라운드에서 바로 연산이 시작되므로, 작업 시점을 세밀하게 제어할 수 있다.

 

직관성과 유연성: 별도의 프레임워크 기능에 의존하지 않고 파이썬 표준 문법만으로 비동기 로직을 짤 수 있다. 특히 여러 비동기 작업을 병렬로 묶어서 실행하거나(asyncio.gather), 특정 시간 안에 끝나지 않으면 취소하는 등의 정교한 핸들링이 가능하다.

 

2. Backgrounds

FastAPI에서 자체적으로 제공하는 기능으로, Starlette의 기능을 상속받아 구현되어 있다. asyncio와의 결정적인 차이는 응답이 클라이언트에게 완전히 전달된 직후에 작업이 시작된다는 점이다.

rom fastapi import FastAPI, BackgroundTasks

app = FastAPI()

def write_log(message: str):
    with open("log.txt", "a") as f:
        f.write(message)

@app.post("/send-notification/{email}")
async def send_notification(email: str, background_tasks: BackgroundTasks):
    # 1. 응답을 먼저 보내고
    # 2. 그 다음에 write_log가 실행됨
    background_tasks.add_task(write_log, f"notification sent to {email}")
    return {"message": "Notification sent in the background"}

실행 시점만 다른 게 아니다. 실무적인 관점, 특히 안정성이 중요한 백엔드 개발자 입장에서 사용하기에는 BackgroundTasks가 asyncio보다 장점이 있다.

 

1. 프레임워크가 보장하는 생명주기(Lifecycle): asyncio는 파이썬 엔진에 작업을 던져놓는 것이라, 요청(Request)이 끝나버리면 그 태스크가 어떻게 되든 프레임워크는 신경 쓰지 않지만, BackgroundTasks는 FastAPI가 응답을 보낸 직후에 실행되도록 직접 관리해준다. 덕분에 의존성 주입(Depends)을 통한 DB 세션 관리 등이 훨씬 안전하다.

 

2. 동기(Sync) 함수의 간편한 처리: asyncio는 반드시 async def 함수만 다룰 수 있지만, BackgroundTasks는 일반 def 함수를 넣어도 FastAPI가 알아서 별도의 스레드 풀에서 돌려준다. 메인 루프를 멈추지 않으면서도 기존의 동기 라이브러리들을 그대로 쓸 수 있다는 건 큰 장점이다.

 

3. 리소스 관리의 안정성: asyncio는 태스크 객체의 참조가 끊기면 가비지 컬렉터(GC)가 실행 중인 작업을 날려버릴 위험이 있다. 하지만 BackgroundTasks는 프레임워크 내부에서 작업 리스트를 들고 있어 작업이 끝날 때까지 참조를 유지한다. 결과적으로 작업이 중간에 증발할 확률이 훨씬 낮다.

 

마치며

FastAPI는 스프링보다 가벼워 사용성이 좋고, 특히 비동기 동작에 대한 디버깅을 실제 동작처럼 확인하며 구현할 수 있다는 점이 큰 매력이었다.

 

하지만 이번 프로젝트에서는 서버 시작 시점부터 백그라운드 루프가 계속 돌아야 하는 구조였기에, 선택의 여지 없이 asyncio를 베이스로 구현하게 됐다. 파이썬의 백그라운드 루프에서의 트랜잭션 관리는 스프링의 @Transactional에 익숙했던 나에게는 꽤나 복잡했다.

 

백그라운드 루프는 HTTP 요청과는 독립적으로 돌기 때문에, FastAPI의 표준인 Depends(get_db) 방식을 쓸 수 없었다. 결국 SessionLocal() 팩토리를 직접 호출하여 매 루프마다 세션을 수동으로 열고 닫아야 했다.

 

스프링에서는 어노테이션 하나로 해결되던 선언적 트랜잭션의 편리함을 뒤로하고, 파이썬에서는 세션 객체를 메서드마다 인자로 넘겨주거나 직접 생명주기를 관리해야 하는 '명시적'인 수고로움이 뒤따랐다. 그동안 당연하게 누려왔던 프레임워크의 마법이 얼마나 고마운 것이었는지 새삼 깨닫는 시간이었다.

'개발' 카테고리의 다른 글

D-ID AI Text to Video Generator 사용해보기(한글 사용 가능)  (0) 2023.11.19
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/03   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함