ASGI와 uvicorn

Python 웹 서버의 구동 원리

ASGI는 Python 비동기 웹 애플리케이션의 인터페이스 표준이고, uvicorn은 이 표준을 구현하는 고성능 서버이다. WSGI와의 차이, uvicorn 설정, 워커 프로세스 구성, 프로덕션 배포 시 gunicorn + uvicorn 조합을 정리한다.

Engineering
저자

Kwangmin Kim

공개

2026년 05월 05일

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 기본 실행

# 설치
pip install uvicorn

# 실행
uvicorn src.services.api.main:app --host 0.0.0.0 --port 8000

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 워커 수 결정

# 경험적 공식
workers = (2 * CPU_CORES) + 1

# 4코어 머신
uvicorn app:app --workers 9

그러나 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 30

gunicorn이 담당하는 역할:

  • 워커 프로세스 관리 (생성, 모니터링, 재시작)
  • 시그널 처리 (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 개발 워크플로

# 개발 (자동 재시작)
uvicorn src.services.api.main:app --reload --port 8000

# 로컬 테스트 (프로덕션 유사 환경)
uvicorn src.services.api.main:app --workers 2 --port 8000

# Docker 빌드 및 테스트
docker build -t agent-api .
docker run -p 8000:8000 agent-api

# 프로덕션 배포
docker compose up -d

7 관련 주제

선행 지식

후속 주제

다른 카테고리 연결

Subscribe

Enjoy this blog? Get notified of new posts by email: