FastAPI는 경로 작업에 async def와 일반 def를 모두 지원합니다. 이 선택은 성능에 중요합니다. 논블로킹 I/O를 await할 수 있을 때는 **async def**를, 코드가 블로킹(동기) 작업을 호출할 때는 일반 **def**를 사용하세요.
@app.get("/async")
async def async_endpoint():
data = await fetch_from_api() # 논블로킹 I/O를 await — 효율적
return data
@app.get("/sync")
def sync_endpoint():
data = blocking_db_call() # 일반적인 블로킹 코드
return data
async def → 메인 이벤트 루프에서 실행. 논블로킹 호출을 await할 때만 효율적.
⚠️ async def 내부의 블로킹 호출은 전체 이벤트 루프를 막아 → 동시성을 무력화!
def → FastAPI가 스레드 풀에서 실행하므로, 블로킹 코드가 이벤트 루프를 막지 않음.
동기/블로킹 라이브러리에 안전함.
# ✅ async def — async 라이브러리(httpx, async DB 드라이버)를 await할 수 있을 때
async def get_user():
async with httpx.AsyncClient() as c:
return await c.get(url)
# ✅ 일반 def — 동기/블로킹 라이브러리(requests, 동기 ORM)를 사용할 때
def get_user():
return requests.get(url).json() # 블로킹 → FastAPI가 스레드에서 실행
# ❌ 위험한 실수 — async def 내부의 블로킹 호출
async def bad():
return requests.get(url).json() # 이벤트 루프를 막음! def 또는 async 클라이언트 사용
핵심 규칙: async def 안에 절대 블로킹 호출을 넣지 마세요. 이벤트 루프를 막아 동시성을 파괴합니다. async 라이브러리를 await와 함께 사용하거나, 일반 def(FastAPI가 안전하게 스레드 풀에서 실행)를 사용하세요.
async def와 def 사이에서 올바르게 선택하는 것은 API의 성능과 동시성에 직접 영향을 미치며, 잘못 선택하는 것은 흔하고 심각한 실수입니다.
핵심 통찰은 async def는 논블로킹 작업을 await할 때만 유익하다는 것입니다. async def 안에 블로킹 호출(예: 동기 requests 라이브러리나 블로킹 데이터베이스 드라이버)을 넣으면 전체 이벤트 루프를 막아, 모든 동시 요청을 멈추게 하고 async의 목적을 무력화합니다.
반대로 FastAPI는 일반 def 엔드포인트를 영리하게 스레드 풀에서 실행하므로, 동기/블로킹 코드가 거기서는 안전합니다.
이 규칙—진정한 async 라이브러리에는 async def, 블로킹 코드에는 일반 def를 사용하고, async def에 블로킹 호출을 절대 섞지 않는 것—을 이해하는 것은 성능 좋은 FastAPI 엔드포인트를 작성하고, 모든 요청이 막힌 이벤트 루프 뒤에서 직렬화되는 미묘하지만 치명적인 실수를 피하는 데 필수적입니다.