Django vs FastAPI, WSGI와 ASGI로 이해하기
Django vs FastAPI
들어가며
Reddit에서 Django Ninja라는 걸 보고 궁금해서 유튜브를 뒤지다가, 정작 엉뚱한 영상에 꽂혔다. 파이썬 백엔드 생태계에는 재밌는 패턴이 하나 있는데, ML 엔지니어들은 FastAPI를 쓰고 전통적인 백엔드 엔지니어들은 Django를 쓴다는 거다. 같은 파이썬인데 직군에 따라 선호가 이렇게 깔끔하게 갈리는 게 신기해서, 검색하던 김에 그 배경까지 정리해봤다.
결론부터 말하면 두 프레임워크가 서 있는 웹 인터페이스 표준이 다르기 때문이다. CGI에서 WSGI를 거쳐 ASGI로 이어진 흐름을 따라가 보자.
프레임워크만으로는 부족하다
runserver나 uvicorn --reload로 띄우는 개발 서버는 운영 환경에 그대로 쓸 수 없다. SSL이나 보안 헤더 같은 기본 방어가 없고, 정적 파일을 파이썬이 직접 서빙하는 건 성능상 낭비고, 로드 밸런싱이나 캐싱도 없다. 애초에 디버깅용으로 만들어진 물건이다.
그래서 운영 환경은 역할을 셋으로 나눈다.
- Web Server (Nginx, Apache) — 정문 역할. 정적 파일 서빙, SSL 터미네이션, 로드 밸런싱
- WAS (Gunicorn, Uvicorn) — 파이썬 인터프리터를 띄우고 프로세스/스레드를 관리
- Framework (Django, FastAPI) — 비즈니스 로직, ORM, 라우팅, 인증
웹 서버와 파이썬은 어떻게 대화하나
C로 짜인 Nginx가 파이썬 코드를 직접 실행할 수는 없다. 그래서 둘 사이에 통역사 역할을 하는 규격이 필요한데, 이게 게이트웨이 인터페이스다.
하는 일은 세 가지다. HTTP 요청을 파이썬이 이해할 객체로 파싱하고, 헤더·쿼리·메서드 같은 메타데이터를 넘겨주고, 애플리케이션이 돌려준 데이터를 다시 HTTP 응답으로 포장한다.
이 규격이 CGI → WSGI → ASGI 순으로 발전해 왔다.
CGI, WSGI, ASGI
CGI는 1990년대 초의 방식이다. 요청이 올 때마다 프로세스를 새로 만들어 처리하고 죽인다. 요청끼리 완전히 독립적이라 하나가 터져도 전체로 번지지 않는다는 장점은 있지만, 매번 프로세스를 띄우는 오버헤드가 어마어마하다. 파이썬처럼 인터프리터를 초기화해야 하는 언어는 더 심각하다.
WSGI는 2003년에 제정된 동기식 표준이다. 워커(프로세스/스레드) 풀을 미리 만들어 두고 요청을 나눠 처리한다. 매번 프로세스를 만들지 않으니 CGI보다 훨씬 빠르다.
한계는 워커 하나가 요청 하나를 붙잡고 끝날 때까지 놓지 않는다는 점이다. 외부 API 호출이나 무거운 I/O가 걸리면 그 워커는 대기하는 동안 아무 일도 못 한다.
여기서 자주 나오는 오해 하나. WSGI라고 서버 전체가 요청을 하나씩만 처리하는 건 아니다. Gunicorn 워커가 4개면 동시에 4개를, gthread나 gevent를 쓰면 훨씬 많이 처리한다. 블로킹은 서버 단위가 아니라 워커 단위로 일어난다.
ASGI는 2016년에 나온 비동기 표준이다. 이벤트 루프를 써서 I/O 대기 시간에 다른 요청을 처리하고, HTTP뿐 아니라 WebSocket과 HTTP/2도 지원한다. 실시간 통신과 스트리밍 응답이 여기서 나온다.
그래서 왜 갈리는가
흔히 "Django는 동기, FastAPI는 비동기"라고 정리하는데, 지금 시점에선 이게 정확하지 않다.
FastAPI
처음부터 비동기를 전제로 설계된 async-first 프레임워크다. 라우팅과 검증, 문서화에 집중한 가벼운 구조다.
ML 쪽에서 FastAPI를 쓰는 이유는 보통 이렇게 정리된다. Pydantic 타입 힌트만으로 검증과 직렬화가 붙으니 실험 코드에서 API로 넘어가는 비용이 적고, Swagger 문서가 자동으로 생성되니 다른 팀에 넘길 때 편하다. 모델 서빙에는 ORM도 Admin도 세션 인증도 필요 없어서 가벼운 구조가 오히려 맞고, LLM 토큰 스트리밍 같은 패턴은 ASGI 위에서 자연스럽게 구현된다.
여기서 짚고 갈 게 하나 있다. "GPU가 추론하는 동안 CPU가 다른 요청을 처리한다"는 설명을 종종 보는데, 이건 실제와 다르다. 모델 추론은 I/O 바운드가 아니라 연산 바운드다. async def 안에서 무거운 추론을 그대로 돌리면 이벤트 루프가 통째로 막혀서 오히려 상황이 나빠진다.
FastAPI에서는 무거운 동기 작업을 그냥 def로 선언하는 게 안전하다. FastAPI가 알아서 threadpool로 넘겨 이벤트 루프를 지켜주기 때문이다. 비동기의 진짜 이점은 추론 자체가 아니라 외부 API 호출, DB 조회, 스트리밍 응답에서 나온다.
Django
"Batteries-included" 철학의 전통 강자다. ORM, 인증, Admin, CSRF 방어가 검증된 형태로 이미 다 들어 있다. 복잡한 비즈니스 로직을 다루는 일반적인 백엔드에서는 이게 압도적인 이점이다. 대부분의 CRUD는 동기로도 충분히 빠르고, Gunicorn 멀티프로세스만으로도 상당한 트래픽을 감당한다.
그리고 Django도 이미 비동기를 지원한다. 3.0부터 ASGI를 공식 지원하고, 3.1부터 async def 뷰를 쓸 수 있고, Channels를 붙이면 WebSocket도 된다.
그런데도 "Django = 동기"라는 인식이 남아 있는 건 생태계의 무게중심이 여전히 동기 쪽이기 때문이다. ORM을 비롯한 핵심 구성 요소와 서드파티 패키지 대부분이 동기 전제로 만들어져 있어서, 비동기 뷰를 써도 결국 sync_to_async로 감싸야 하는 지점을 만나게 된다.
차이를 한 줄로 줄이면 이렇다. FastAPI는 비동기가 기본값이고, Django는 비동기가 선택지다.
마무리
정리하면 선택 기준은 꽤 명확하다.
모델 서빙 API나 LLM 스트리밍, WebSocket 실시간 통신, 외부 API 호출이 많은 서비스라면 FastAPI가 맞는다. 반대로 복잡한 데이터 모델과 권한 관리가 필요하고, 관리자 페이지가 있어야 하고, 검증된 생태계 위에서 빠르게 MVP를 만들어야 한다면 Django가 낫다.
CGI에서 ASGI까지의 흐름을 알고 나면 "뭐가 더 좋냐"는 질문이 자연스럽게 "내 문제엔 뭐가 맞냐"로 바뀐다. 결국 그게 전부인 것 같다.