FastAPI에서 async/await & def vs async def 정리

adc0612·2026년 1월 11일

FastAPI

목록 보기
1/1

FastAPI를 공부하면서 가장 헷갈렸던 부분이
“언제 def를 쓰고 언제 async def를 써야 하는가?”,
그리고 Starlette / async-await의 정확한 역할이었다.

이 글은 개념 정리 + 실무에서 기억해야 할 규칙 위주로 정리한 기록이다.

1. FastAPI와 Starlette의 관계

FastAPI는 단독 프레임워크라기보다는 조합형 프레임워크다.

  • Starlette
  • ASGI 기반 웹 프레임워크
  • 라우팅, 미들웨어, Request/Response, WebSocket, BackgroundTasks 담당
  • 비동기 친화적 구조
  • Pydantic
  • 데이터 검증, 직렬화
  • FastAPI
  • 타입 힌트 기반 라우팅
  • 의존성 주입(Depends)
  • OpenAPI / Swagger 자동 생성

👉 실제 요청 처리 흐름의 대부분은 Starlette가 담당한다.


2. ASGI와 비동기 모델

Starlette/FastAPI는 ASGI(Async Server Gateway Interface) 기반이다.

  • WSGI (Flask/Django 초기)
  • 동기 처리 중심
  • ASGI
  • 비동기 기본
  • WebSocket, long-lived connection 지원
  • 하나의 이벤트 루프에서 여러 요청을 효율적으로 처리

ASGI 서버(Uvicorn 등)는 요청을 받아
→ 이벤트 루프에서 coroutine을 스케줄링한다.


3. async / await 핵심 개념 (Python 기준)

async def

  • 함수를 실행하는 게 아니라 coroutine 객체를 생성
  • 실제 실행은 이벤트 루프가 await로 처리
async def foo():
    print("hi")

foo()        # 실행 ❌ (coroutine 객체 생성)
await foo()  # 실행 ⭕

await

  • “지금은 I/O 기다려야 하니까 이 작업은 멈추고 제어권을 이벤트 루프에 반환”
  • 스레드를 멈추는 게 아니라 작업을 양보(yield)

이벤트 루프

  • 실행 가능한 coroutine들을 관리
  • I/O 대기 중인 작업은 잠시 멈추고
  • 준비된 작업부터 다시 실행

👉 한 스레드로 많은 요청을 처리할 수 있는 이유


4. JavaScript async/await과의 비교

공통점

  • async/await 개념과 역할은 거의 동일
  • 이벤트 루프 기반
  • I/O 대기 동안 다른 작업 처리

차이점 (중요)

항목JavaScriptPython
기본 모델비동기동기
async 사용기본선택
동기 I/O거의 없음매우 흔함
위험 포인트상대적으로 적음async 안에 sync 코드

👉 Python에서는 async 함수 안에 동기 I/O를 넣으면 이벤트 루프가 막힌다


5. FastAPI에서 def vs async def 결정 규칙 (핵심)

한 문장 요약

await 가능한 비동기 I/O를 쓰면 async def,
아니면 def

async def를 써야 하는 경우

•	async HTTP 클라이언트 (httpx.AsyncClient)
•	async DB 드라이버 / ORM
•	Redis async 클라이언트
•	WebSocket
•	StreamingResponse, SSE
•	여러 I/O를 동시에 처리 (asyncio.gather)

👉 await가 있는 설계

def를 쓰는 게 좋은 경우

•	동기 HTTP (requests)
•	동기 DB (SQLAlchemy sync, psycopg2 등)
•	boto3 같은 동기 SDK
•	CPU 바운드 작업 (이미지 처리, 압축, ML 추론 등)

👉 FastAPI는 def 엔드포인트를 자동으로 스레드풀에서 실행해서 이벤트 루프를 보호한다.


6. 가장 흔한 실수

❌ async def 안에서 동기 I/O

async def endpoint():
    r = requests.get("https://example.com")  # ❌
    return r.json()
•	이벤트 루프 전체가 멈춤
•	다른 요청 처리 불가
•	서버가 느려지거나 멎은 것처럼 보임

해결:

•	def endpoint()로 변경
•	또는 진짜 async 클라이언트 사용

7. 의존성(Depends)도 동일한 규칙

  • Depends 함수도 def / async def 선택 가능
  • 규칙은 엔드포인트와 동일
내부로직권장
async I/Oasync def
sync I/Odef

8. 실무 기준 빠른 체크리스트

async def를 써야 하는 경우

  1. 비동기 I/O 라이브러리를 사용한다
    • HTTP: httpx.AsyncClient, aiohttp
    • DB: async driver/ORM (예: async SQLAlchemy + asyncpg)
    • Redis: redis.asyncio
    • 비동기 파일/스트리밍 등

  2. WebSocket, StreamingResponse, 서버센트이벤트(SSE) 같이
    연결을 오래 붙잡고 중간중간 await로 양보해야 한다

  3. “여러 I/O를 동시에” 처리하고 싶다
    • await asyncio.gather(...) 같은 패턴

👉 요약: await로 “기다리는 구간”이 있는 설계면 async def

def를 쓰는 게 더 좋은/안전한 경우

  1. 내부에서 동기 I/O를 한다
    • requests
    • (동기) SQLAlchemy + psycopg2, pymysql 등
    • boto3(대부분 동기), 각종 레거시 SDK
  2. CPU 바운드 작업이 크다
    • 이미지/영상 처리, 큰 압축/암호화, 무거운 통계/파싱, ML 추론 등
    (이걸 async def에서 돌리면 이벤트 루프를 막아서 전체 서버가 느려짐)
  3. 라이브러리가 async 지원이 애매하거나, 팀이 일단 sync 스택으로 간다

👉 요약: “await가 없는 코드”면 def가 기본값

작업 종류권장이유
async HTTP/DB/Redis 등 await 가능한 I/Oasync defawait로 양보 가능
sync HTTP(requests)/sync DB/레거시 SDKdef스레드풀로 격리
WebSocket / 스트리밍 / SSEasync def협력적 양보 필요
무거운 CPU 연산def + 워커/프로세스이벤트 루프 보호

9. 기억해야 할 한 줄 요약

FastAPI에서 async는 “성능 마법”이 아니라
“이벤트 루프를 막지 않기 위한 약속”이다.
sync 코드를 async로 감싸는 건 최악의 선택이 될 수 있다.


10. 개인 메모 (내가 기억할 포인트)

•	async는 I/O 대기 최적화
•	CPU 작업은 async로 해결 안 됨
•	async def 안에 sync 코드 ❌
•	def는 느린 게 아니라 안전한 선택일 수 있음
•	“라이브러리가 진짜 async인지” 항상 확인

1개의 댓글

comment-user-thumbnail
2026년 3월 22일

유익한 글 잘 보고 갑니다.

답글 달기