1 웹 서버와 애플리케이션의 관계
Python 웹 프레임워크(FastAPI, Django, Flask)는 HTTP 요청을 직접 받지 않는다. 웹 서버가 네트워크 연결을 관리하고, 요청을 파싱하여 애플리케이션에 전달한다.
클라이언트 → [웹 서버 (uvicorn)] → [애플리케이션 (FastAPI)]
네트워크 관리 비즈니스 로직
HTTP 파싱 라우팅, 응답 생성
웹 서버와 애플리케이션 사이에는 인터페이스 표준이 필요하다. 이 표준이 있으면 어떤 서버든 어떤 프레임워크든 조합하여 사용할 수 있다.
2 WSGI vs ASGI
2.1 WSGI (Web Server Gateway Interface)
Python의 전통적인 웹 인터페이스 표준이다. Flask, Django(기본 모드)가 사용한다.
# WSGI 애플리케이션의 구조
def application(environ, start_response):
start_response("200 OK", [("Content-Type", "text/plain")])
return [b"Hello, World"]WSGI는 동기 방식이다. 하나의 요청을 처리하는 동안 해당 워커는 다른 요청을 받지 못한다. LLM 호출처럼 수 초간 외부 API 응답을 기다리는 경우, 그 동안 워커가 아무것도 하지 못하고 점유된다.
2.2 ASGI (Asynchronous Server Gateway Interface)
ASGI는 WSGI의 비동기 후속 표준이다. FastAPI, Django(ASGI 모드)가 사용한다.
# ASGI 애플리케이션의 구조
async def application(scope, receive, send):
await send({
"type": "http.response.start",
"status": 200,
"headers": [[b"content-type", b"text/plain"]],
})
await send({
"type": "http.response.body",
"body": b"Hello, World",
})ASGI는 async/await를 지원한다. LLM 응답을 기다리는 동안 다른 요청을 처리할 수 있어 동시 처리량이 크게 향상된다.
| 기준 | WSGI | ASGI |
|---|---|---|
| 동기/비동기 | 동기 | 비동기 |
| WebSocket | 미지원 | 지원 |
| SSE | 제한적 | 지원 |
| HTTP/2 | 미지원 | 지원 |
| 동시 처리 | 워커 수 = 동시 요청 수 | 단일 워커가 수백 요청 처리 |
| 서버 예시 | gunicorn, uWSGI | uvicorn, hypercorn, daphne |
| 프레임워크 | Flask, Django | FastAPI, Django (ASGI 모드) |
2.3 AI Agent 서빙에서 ASGI가 중요한 이유
LLM 호출은 네트워크 I/O가 대부분이다. 사용자 A의 LLM 응답을 기다리는 동안 사용자 B의 요청을 처리할 수 있어야 한다.
WSGI (동기):
요청 A → [LLM 호출 5초 대기...] → 응답 A
요청 B는 워커가 없어서 대기
ASGI (비동기):
요청 A → [LLM 호출 시작, await] → ... → 응답 A
요청 B → [LLM 호출 시작, await] → ... → 응답 B
요청 C → [처리] → 응답 C
(하나의 워커가 여러 요청을 동시 처리)
동시 사용자 10명이 LLM을 호출하면, WSGI는 워커 10개가 필요하고, ASGI는 워커 1개로 처리할 수 있다.
3 uvicorn
uvicorn은 ASGI 서버의 사실상 표준이다. FastAPI 공식 문서에서 권장하는 서버이다.
3.1 기본 실행
src.services.api.main:app에서:
src.services.api.main은 Python 모듈 경로이다 (파일:src/services/api/main.py)app은 해당 모듈 내 FastAPI 인스턴스 변수명이다
3.2 주요 옵션
uvicorn src.services.api.main:app \
--host 0.0.0.0 \ # 모든 인터페이스에서 수신 (Docker 필수)
--port 8000 \ # 포트 번호
--reload \ # 코드 변경 시 자동 재시작 (개발용)
--workers 4 \ # 워커 프로세스 수 (프로덕션용)
--log-level info \ # 로그 레벨 (debug, info, warning, error)
--access-log \ # 접속 로그 출력
--timeout-keep-alive 30 # Keep-Alive 타임아웃 (초)| 옵션 | 개발 환경 | 프로덕션 환경 |
|---|---|---|
--reload |
O | X (성능 오버헤드) |
--workers |
1 (기본) | CPU 코어 수 기반 |
--host |
127.0.0.1 (기본) |
0.0.0.0 |
--log-level |
debug |
info 또는 warning |
3.3 –host 0.0.0.0의 의미
127.0.0.1(기본값)은 로컬에서만 접근 가능하다. Docker 컨테이너 안에서 실행할 때 0.0.0.0으로 설정해야 컨테이너 외부에서 접근할 수 있다.
127.0.0.1: 컨테이너 내부에서만 접근 가능 → docker run -p 8000:8000 해도 외부에서 접근 불가
0.0.0.0: 모든 네트워크 인터페이스에서 수신 → 외부 접근 가능
4 워커 프로세스
4.1 워커 수 결정
그러나 AI Agent 서빙에서는 이 공식이 항상 적용되지 않는다:
- ASGI는 비동기이므로 적은 워커로도 높은 동시성을 달성한다
- LLM 호출이 주된 작업이면 워커 수를 늘려도 성능 향상이 제한적이다
- 메모리: 각 워커가 모델, 벡터 인덱스 등을 로드하면 메모리 사용량이 워커 수에 비례한다
AI Agent 서버에서는 --workers 2~4로 시작하고, 부하 테스트를 통해 조정하는 것이 현실적이다.
4.2 gunicorn + uvicorn 워커
프로덕션에서는 gunicorn을 프로세스 매니저로 사용하고, uvicorn을 워커로 사용하는 조합이 일반적이다.
pip install gunicorn uvicorn
gunicorn src.services.api.main:app \
--worker-class uvicorn.workers.UvicornWorker \
--workers 4 \
--bind 0.0.0.0:8000 \
--timeout 120 \
--graceful-timeout 30gunicorn이 담당하는 역할:
- 워커 프로세스 관리 (생성, 모니터링, 재시작)
- 시그널 처리 (SIGTERM 시 graceful shutdown)
- 워커가 응답하지 않으면 자동 재시작 (
--timeout) - 로그 통합
uvicorn 단독으로도 --workers를 지원하지만, gunicorn의 프로세스 관리가 더 안정적이다.
5 프로덕션 배포 구성
5.1 Dockerfile에서의 실행
# 개발용 — 단일 워커, 자동 재시작 불필요
CMD ["uvicorn", "src.services.api.main:app", "--host", "0.0.0.0", "--port", "8000"]
# 프로덕션용 — gunicorn + uvicorn 워커
CMD ["gunicorn", "src.services.api.main:app", \
"--worker-class", "uvicorn.workers.UvicornWorker", \
"--workers", "4", \
"--bind", "0.0.0.0:8000", \
"--timeout", "120"]5.2 전체 프로덕션 스택
인터넷
│
Nginx / 클라우드 로드밸런서 (443)
│ SSL 종료, 정적 파일 서빙, 프록시
│
gunicorn (8000)
│ 프로세스 관리
├── uvicorn worker 1 ── FastAPI app
├── uvicorn worker 2 ── FastAPI app
├── uvicorn worker 3 ── FastAPI app
└── uvicorn worker 4 ── FastAPI app
각 레이어의 역할:
- Nginx: SSL 종료, 정적 파일 서빙, 로드 밸런싱, 요청 버퍼링
- gunicorn: 워커 프로세스 관리, 헬스체크, 자동 재시작
- uvicorn: ASGI 프로토콜 처리, 비동기 요청 처리
- FastAPI: 라우팅, 비즈니스 로직, Pydantic 검증
5.3 타임아웃 설정
LLM 호출은 수 초~수십 초가 걸리므로 타임아웃을 충분히 설정해야 한다.
gunicorn app:app \
--timeout 120 \ # 워커 타임아웃 (LLM 응답 대기 고려)
--graceful-timeout 30 \ # 종료 시 진행 중 요청 완료 대기
--keep-alive 30 # Keep-Alive 연결 유지 시간Nginx에서도 대응하는 설정이 필요하다:
location /agents/ {
proxy_pass http://localhost:8000;
proxy_read_timeout 120s; # 백엔드 응답 대기
proxy_send_timeout 120s;
}각 레이어의 타임아웃이 일관되지 않으면 요청이 중간에 끊어진다. Nginx 타임아웃이 gunicorn보다 짧으면 “502 Bad Gateway”가 발생한다.
6 개발 워크플로
7 관련 주제
선행 지식
- FastAPI 입문 – uvicorn으로 실행하는 프레임워크
- Docker 기초 – 컨테이너에서의 서버 실행
후속 주제
- CORS와 Proxy – Nginx Reverse Proxy 구성
- SSE – 실시간 스트리밍 – 비동기 서버가 스트리밍을 처리하는 원리
다른 카테고리 연결
- Async Programming – Python async/await 기초