한양과학기술고등학교

에듀테크 프로젝트 대시보드

async vs sync — 동시성 모델

async def 이벤트 루프 스레드풀 블로킹 run_in_threadpool

선택을 잘못하면 성능이 오히려 나빠집니다. 핵심은 '이벤트 루프를 블로킹하지 마라'.

1. FastAPI 가 라우트를 실행하는 방식

선언실행 위치쓸 때
async def이벤트 루프에서 직접내부가 전부 await 가능한 비동기 I/O
def외부 스레드풀(anyio)동기 DB 드라이버, requests, 파일 I/O, 무거운 CPU

2. 안티패턴 — async 안에서 블로킹

import time, requests

@app.get("/bad")
async def bad():
    time.sleep(3)                 # 이벤트 루프 전체가 3초 멈춤
    r = requests.get("https://...")  # 동기 HTTP — 역시 블로킹
    return r.json()

#  -> 이 3초 동안 서버가 다른 요청을 하나도 처리 못 함

3. 올바른 방법 A — 비동기 라이브러리

import asyncio, httpx

@app.get("/good")
async def good():
    await asyncio.sleep(3)
    async with httpx.AsyncClient() as client:
        r = await client.get("https://...")
    return r.json()

4. 올바른 방법 B — 그냥 def 로

@app.get("/also-good")
def also_good():
    r = requests.get("https://...")   # 스레드풀에서 실행되므로 OK
    return r.json()

5. async 함수 안에서 어쩔 수 없이 블로킹 호출

from fastapi.concurrency import run_in_threadpool

@app.get("/mix")
async def mix():
    data = await run_in_threadpool(heavy_sync_parse, big_blob)
    return data

6. 병렬 처리

import asyncio

@app.get("/dashboard")
async def dashboard():
    users, orders, stats = await asyncio.gather(
        fetch_users(), fetch_orders(), fetch_stats(),
    )
    return {"users": users, "orders": orders, "stats": stats}
"명시적 의존성 주입은 테스트를 쉽게 만든다"