Backend/Python

Python 웹 서버 개념 정리 — WSGI, ASGI, Uvicorn, Gunicorn 그리고 FastAPI 아키텍처 패턴

포도쿵야 2026. 4. 6. 10:12

Python으로 API 서버를 처음 만들다 보면 uvicorn app:app --host 0.0.0.0 --port 8000 명령어를 자연스럽게 쓰게 된다. 그런데 왜 Uvicorn인지, Gunicorn과는 무엇이 다른지, 프로덕션에서는 어떻게 조합해서 쓰는지 정확히 설명할 수 있는가.

이 글에서는 그 개념부터 실무 아키텍처까지 순서대로 정리한다.


📌 WSGI vs ASGI — 먼저 이 차이부터

Uvicorn과 Gunicorn을 비교하기 전에 WSGI와 ASGI를 먼저 이해해야 한다. 이 둘은 Python 웹 애플리케이션과 웹 서버 사이의 통신 규약(인터페이스 표준) 이다.

구분 WSGI ASGI

정식 명칭 Web Server Gateway Interface Asynchronous Server Gateway Interface
표준 PEP 3333 (전통적인 표준) WSGI의 후속 표준
처리 방식 동기(synchronous) — 요청 하나를 처리하는 동안 다음 요청은 대기 비동기(async/await) — I/O 대기 중에 다른 요청 처리 가능
대표 프레임워크 Django, Flask FastAPI, Django Channels
대표 서버 Gunicorn Uvicorn
적합한 작업 CPU 연산 중심 I/O 바운드 (DB 조회, 외부 API 호출)

왜 FastAPI는 ASGI인가? FastAPI는 DB 조회, 외부 API 호출처럼 I/O 대기가 잦은 백엔드 서비스에서 쓰인다. ASGI 기반이면 하나의 요청이 DB 응답을 기다리는 동안 다른 요청을 처리할 수 있어서 동시 처리 효율이 높다. WSGI 기반이었다면 DB 응답을 기다리는 동안 다른 요청은 줄을 서야 한다.


📌 Uvicorn — ASGI 서버

Uvicorn은 ASGI 표준을 구현한 웹 서버다. uvloop와 httptools 기반으로 매우 빠른 비동기 처리를 제공한다. FastAPI 공식 권장 서버다.

# 기본 실행
uvicorn app:app --host 0.0.0.0 --port 8000

# 개발 환경: 코드 변경 시 자동 재시작
uvicorn app:app --host 0.0.0.0 --port 8000 --reload

# 워커 수 지정 (프로덕션에서는 Gunicorn에 위임하는 편이 나음)
uvicorn app:app --host 0.0.0.0 --port 8000 --workers 4

Uvicorn 단독 사용의 한계 Uvicorn만으로도 --workers 옵션으로 멀티 프로세스를 띄울 수 있지만, 프로세스 관리(장애 복구, graceful reload, 워커 모니터링)는 Uvicorn의 영역이 아니다. 프로덕션에서는 Gunicorn이 이 역할을 담당하고 Uvicorn은 각 워커의 실행 엔진으로 사용하는 것이 일반적이다.


📌 Gunicorn — 프로세스 매니저

Gunicorn(Green Unicorn)은 원래 WSGI 서버지만, 워커 클래스(Worker Class)를 교체할 수 있는 구조 덕분에 ASGI 앱도 실행할 수 있다. FastAPI + Uvicorn 조합에서는 Gunicorn이 프로세스 관리자로, Uvicorn이 각 워커의 실행 엔진으로 역할을 나눈다.

[ Gunicorn + Uvicorn Worker 구조 ]

Gunicorn (마스터 프로세스)
  ├─ Uvicorn Worker 1  ← 실제 ASGI 요청 처리
  ├─ Uvicorn Worker 2
  ├─ Uvicorn Worker 3
  └─ Uvicorn Worker 4

Gunicorn 역할: 워커 생성/종료, 장애 복구, graceful reload, 시그널 처리
Uvicorn 역할: 각 워커에서 비동기 요청 처리
# Gunicorn + UvicornWorker 조합 (프로덕션 권장)
gunicorn app:app \
  --worker-class uvicorn.workers.UvicornWorker \
  --workers 4 \
  --bind 0.0.0.0:8000 \
  --timeout 120 \
  --keep-alive 5

# 워커 수 공식: (CPU 코어 수 × 2) + 1 이 일반적인 시작점
# I/O 바운드 작업이 많으면 코어 수보다 더 늘리기도 함

🔍 Uvicorn vs Gunicorn — 언제 무엇을 쓸까

항목 Uvicorn 단독 Gunicorn + UvicornWorker

용도 개발 환경 프로덕션
멀티 프로세스 가능하지만 관리 기능 없음 Gunicorn이 완전히 관리
워커 장애 복구 ❌ 없음 ✅ 자동 재시작
Graceful reload ❌ 없음 ✅ 지원
--reload (핫 리로드) ✅ 지원 개발용 아님
설정 복잡도 단순 중간

📌 다른 서버 옵션들

Uvicorn / Gunicorn 외에도 Python API 서버로 사용할 수 있는 옵션들이 있다.

서버 종류 특징

Hypercorn ASGI HTTP/2, HTTP/3(QUIC) 지원. Uvicorn의 대안
Waitress WSGI 순수 Python 구현. Windows 환경 친화적. Django/Flask 프로젝트에서 사용
uWSGI WSGI 고성능이지만 설정 복잡. Nginx 조합으로 오래 쓰였으나 최근 Gunicorn + Uvicorn에 자리를 내줌
Daphne ASGI Django Channels 전용. Django 프로젝트에서 WebSocket 필요 시 사용

📌 Python API 서버 아키텍처 패턴

FastAPI 기반 서비스를 실제로 구성할 때 자주 쓰이는 패턴들을 정리한다. 어떤 패턴이든 핵심은 관심사 분리(Separation of Concerns) 다.

패턴 1 — 계층형 아키텍처 (Layered Architecture)

가장 일반적으로 쓰이는 패턴이다. Router → Service → Repository 순서로 역할을 나눈다. 각 계층은 바로 아래 계층만 호출하고, 위 계층의 존재를 모른다.

HTTP 요청
    ↓
Router        ← 요청 수신, 입력 검증, 응답 직렬화
    ↓
Service       ← 비즈니스 로직, 규칙 처리, 트랜잭션
    ↓
Repository    ← DB 접근, 쿼리 실행, 데이터 반환
    ↓
Database
# routers/user.py — 요청/응답만 담당
@router.get('/users/{user_id}', response_model=UserResponse)
async def get_user(user_id: int, service: UserService = Depends(get_user_service)):
    return await service.get_user(user_id)

# services/user_service.py — 비즈니스 로직만 담당
class UserService:
    async def get_user(self, user_id: int) -> User:
        user = await self.repo.find_by_id(user_id)
        if not user:
            raise HTTPException(status_code=404)
        return user

# repositories/user_repository.py — DB 접근만 담당
class UserRepository:
    async def find_by_id(self, user_id: int) -> User | None:
        return await self.db.query(User).filter_by(id=user_id).first()

패턴 2 — Nginx + Gunicorn + Uvicorn (프로덕션 표준)

대부분의 Python 프로덕션 환경에서 사용하는 구조다. Nginx가 리버스 프록시로 앞단에서 SSL 처리, 정적 파일 서빙, 로드밸런싱을 담당하고, 뒤에서 Gunicorn + Uvicorn 워커가 API 요청을 처리한다.

클라이언트 (브라우저)
    ↓ HTTPS
Nginx               ← SSL 종료, 정적 파일, 로드밸런싱, 요청 제한
    ↓ HTTP (내부)
Gunicorn            ← 프로세스 관리, 워커 감시
  ├─ Uvicorn Worker 1
  ├─ Uvicorn Worker 2
  └─ Uvicorn Worker N
    ↓
FastAPI Application

왜 Nginx를 앞에 두는가? Gunicorn/Uvicorn은 느린 클라이언트(Slow Client Attack) 방어, SSL 처리, 정적 파일 서빙에 특화되어 있지 않다. Nginx가 이 역할을 맡아 처리하고, 빠른 요청만 뒷단으로 전달하면 애플리케이션 서버는 비즈니스 로직에만 집중할 수 있다.

패턴 3 — Docker + 컨테이너 배포

현대적인 배포 환경에서는 컨테이너로 패키징해서 배포한다. Dockerfile 하나로 개발/스테이징/프로덕션 환경을 동일하게 맞출 수 있다.

FROM python:3.11-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

# 프로덕션: Gunicorn + UvicornWorker
CMD ["gunicorn", "app:app",
     "--worker-class", "uvicorn.workers.UvicornWorker",
     "--workers", "4",
     "--bind", "0.0.0.0:8000"]

패턴 4 — 마이크로서비스 / API Gateway

규모가 커지면 단일 FastAPI 앱을 도메인별로 분리하고, API Gateway가 라우팅을 담당하는 구조로 발전한다.

클라이언트
    ↓
API Gateway         ← 인증, 라우팅, 레이트 리밋
  ├─ /auth/*    → Auth Service     (FastAPI)
  ├─ /users/*   → User Service     (FastAPI)
  ├─ /orders/*  → Order Service    (FastAPI)
  └─ /analytics → Analytics Service (FastAPI)

📌 FastAPI 계층형 아키텍처 — 디렉토리 구조

실무에서 자주 쓰이는 FastAPI 프로젝트 구조다. Router / Service / Repository 세 계층으로 나누고, 쿼리와 유틸리티를 별도 디렉토리로 분리한다.

app/
├── main.py                      # FastAPI 인스턴스, 미들웨어, 라우터 등록
│
├── routers/                     # API 엔드포인트 정의
│   ├── auth.py                  # POST /auth/login, POST /auth/refresh
│   ├── user.py                  # GET /users, GET /users/{id}
│   └── ...
│
├── services/                    # 비즈니스 로직
│   ├── base_service.py          # 공통 로직 (날짜 검증, 로깅 등)
│   ├── auth_service.py
│   └── ...
│
├── repositories/                # 데이터 접근
│   ├── base_repository.py       # DB 연결, 공통 쿼리 실행
│   ├── user_repository.py
│   └── ...
│
├── queries/                     # SQL 파일 분리 관리
│   ├── user/
│   │   └── get_user_by_id.sql
│   └── ...
│
├── schemas/                     # Pydantic 모델 (요청/응답 스키마)
│   ├── user.py                  # UserRequest, UserResponse
│   └── ...
│
├── utils/                       # 유틸리티
│   ├── db_connection.py         # DB 엔진, 연결 풀 관리
│   ├── logger_utils.py
│   └── cache_decorator.py
│
└── infrastructure/              # 인프라 설정
    └── dependency_injection.py  # FastAPI Depends 설정

SQL을 별도 파일로 분리하는 이유 복잡한 쿼리를 Python 코드 안에 문자열로 쓰면 가독성이 떨어지고, SQL 전용 에디터 지원(자동완성, 문법 강조)도 받을 수 없다. queries/ 디렉토리에 .sql 파일로 분리하면 SQL은 SQL답게 관리하고, Python 코드는 비즈니스 로직에만 집중할 수 있다.


📌 의존성 주입 (Dependency Injection)

FastAPI의 Depends()는 의존성 주입을 간결하게 처리한다. Router가 Service를 직접 생성하지 않고 주입받는 구조로, 테스트 시 mock으로 교체하기 쉽고 결합도가 낮아진다.

# infrastructure/dependency_injection.py
from fastapi import Depends

def get_user_repository() -> UserRepository:
    return UserRepository(db=get_db_connection())

def get_user_service(
    repo: UserRepository = Depends(get_user_repository)
) -> UserService:
    return UserService(repo=repo)

# routers/user.py
@router.get('/users/{user_id}')
async def get_user(
    user_id: int,
    service: UserService = Depends(get_user_service)  # 주입
):
    return await service.get_user(user_id)

# 테스트 시 mock으로 교체
app.dependency_overrides[get_user_service] = lambda: MockUserService()

📋 정리

항목 내용

WSGI vs ASGI WSGI는 동기 처리 (Flask, Django). ASGI는 비동기 처리 (FastAPI). I/O가 많은 API 서버라면 ASGI가 유리하다
Uvicorn ASGI 서버. 빠르고 가볍다. 개발 환경에서 단독 사용하고, 프로덕션에서는 Gunicorn의 워커 엔진으로 사용
Gunicorn 프로세스 매니저. 워커 생성/종료, 장애 복구, graceful reload 담당. UvicornWorker 클래스와 조합해 FastAPI를 프로덕션에서 실행
프로덕션 표준 구조 Nginx (리버스 프록시) → Gunicorn (프로세스 관리) → Uvicorn Worker (ASGI 처리) → FastAPI
계층형 아키텍처 Router (입출력) → Service (비즈니스 로직) → Repository (DB 접근). 각 계층은 바로 아래 계층만 안다
의존성 주입 Depends()로 Router와 Service의 결합도를 낮춘다. 테스트 시 mock으로 교체하기 쉽다