🔀
FastAPI의 async/await — def와 async def의 차이
async def를 쓰면 빨라지나? 그건 경우에 따라 다르다
FastAPI에서 이 둘은 동작이 완전히 다르다.
# 스레드풀에서 실행
@app.get('/sync')
def sync_endpoint():
result = blocking_io() # OK — 스레드가 블록되어도 다른 스레드가 처리
return result
# 이벤트 루프에서 실행
@app.get('/async')
async def async_endpoint():
result = await async_io() # OK — await 동안 다른 요청 처리 가능
return result
def (일반 함수)
FastAPI가 자동으로 run_in_executor()로 스레드풀에서 실행한다. 블로킹 I/O(동기 DB 드라이버, requests 라이브러리 등)를 써도 이벤트 루프가 막히지 않는다.
async def
이벤트 루프에서 직접 실행된다. await 없이 블로킹 코드를 쓰면 전체 서버가 멈춘다. 이게 가장 흔한 실수.
# ❌ 위험 — 이벤트 루프를 막는다
@app.get('/bad')
async def bad():
result = requests.get('https://...') # 블로킹! 다른 요청 전부 대기
return result
# ✅ 올바른 방법
@app.get('/good')
async def good():
async with httpx.AsyncClient() as client:
result = await client.get('https://...') # 비블로킹
return result
어떨 때 뭘 쓰나
동기 DB(SQLAlchemy sync) →
def비동기 DB(asyncpg, motor) →
async defrequests →
def, httpx →async defCPU 연산(이미지 처리 등) →
def(어차피 GIL 때문에 async 의미 없음)
핵심 포인트
1
def → FastAPI가 스레드풀에서 실행 (블로킹 I/O OK)
2
async def → 이벤트 루프에서 실행 (반드시 await 사용)
3
async def에서 블로킹 코드(requests.get 등) 쓰면 서버 전체 멈춤
4
확실하지 않으면 def를 쓴다 — FastAPI가 알아서 스레드풀 처리
사용 사례
외부 API 호출이 많은 서버 — async + httpx로 동시 요청 처리
기존 동기 코드 마이그레이션 — def로 시작, 점진적으로 async 전환