async def를 붙였는데 다른 요청이 전혀 동시 처리되지 않은 이유
문제 발생
FastAPI로 만든 API에 async def를 붙였는데, 요청 하나가 오래 걸리는 동안 다른 요청이 전혀 처리되지 않고 순서대로 밀려서 처리됐습니다 — 비동기의 이점이 전혀 없어 보였습니다.
@app.get("/report")
async def get_report():
data = requests.get("https://slow-api.example.com") # 동기 블로킹 호출
return process(data)원인 분석
asyncio는 협력형(cooperative) 멀티태스킹입니다. 하나의 이벤트 루프가 여러 코루틴을 번갈아 실행하는데, 이 "번갈아 실행"이 가능한 건 각 코루틴이 await를 만날 때마다 스스로 실행을 양보(yield)하기 때문입니다.
문제는 requests.get(...) 같은 동기(블로킹) 함수는 await가 아닙니다 — 이 함수는 응답이 올 때까지 그 스레드 자체를 완전히 멈춰버립니다. 이벤트 루프는 단일 스레드에서 실행되는 경우가 많으므로, requests.get()이 블로킹되어 있는 동안 이벤트 루프 자체가 다른 어떤 코루틴도 진행시킬 수 없습니다 — async def를 붙였어도 사실상 동기 코드와 똑같이 동작하게 됩니다.
해결 방안
- 비동기 지원 라이브러리로 교체합니다.
requests대신httpx(AsyncClient),aiohttp등 실제로await가능한 비동기 HTTP 클라이언트를 씁니다.
import httpx
@app.get("/report")
async def get_report():
async with httpx.AsyncClient() as client:
response = await client.get("https://slow-api.example.com") # 진짜 non-blocking
return process(response)- 정말 동기 라이브러리를 벗어날 수 없는 경우(레거시 라이브러리, CPU 위주 작업 등)라면, 그 호출을 별도 스레드 풀에서 실행하도록 넘겨 이벤트 루프를 막지 않게 합니다.
import asyncio
@app.get("/report")
async def get_report():
data = await asyncio.to_thread(requests.get, "https://slow-api.example.com")
return process(data)- DB 드라이버도 마찬가지입니다 — 동기 드라이버(
psycopg2등)를async def안에서 그대로 쓰면 같은 문제가 생깁니다. 비동기 드라이버(asyncpg,aiomysql등)나 ORM의 비동기 지원 기능을 씁니다. - "이 함수가 실제로
await할 대상이 있는가"를 항상 확인하는 습관이 필요합니다 —async def를 붙이는 것 자체는 아무것도 자동으로 비동기로 만들어주지 않습니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.