Uvicorn

Uvicorn은 Python용 ASGI(Asynchronous Server Gateway Interface) 웹 서버 구현체이다.

Uvicorn은 비동기 Python 웹 애플리케이션을 위한 고성능 서버이다.
ASGI 프로토콜을 지원하여 HTTP, HTTP2, WebSocket 등의 프로토콜을 처리할 수 있다.

주요 특징

  1. 비동기 처리: asyncio를 기반으로 하여 비동기 코드를 효율적으로 실행한다.
  2. 고성능: uvloop와 httptools를 사용하여 빠른 속도를 제공한다.
  3. 경량화: 최소한의 의존성으로 설치 가능하다.
  4. 개발 편의성: 자동 리로드 기능을 제공하여 개발 시 편리하다.
  5. ASGI 호환성: ASGI 표준을 준수하여 다양한 ASGI 프레임워크와 호환된다.

FastAPI와의 통합

Uvicorn은 FastAPI의 기본 웹 서버로 사용된다.
FastAPI는 Uvicorn의 비동기 처리 능력을 활용하여 고성능 API를 구현할 수 있다.

최적화

Uvicorn은 uvloop를 사용하여 asyncio의 성능을 2-4배 향상시킬 수 있다.
또한 httptools를 사용하여 HTTP 파싱 속도를 개선한다.

Uvloop

uvloop는 Cython으로 작성된 매우 빠른 Python 이벤트 루프 구현체이다.
libuv(Node.js가 사용하는 동일한 이벤트 루프 엔진)를 기반으로 하여 Python의 기본 asyncio 이벤트 루프를 대체한다.

기본적인 사용방법:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import uvloop
import asyncio

# uvloop를 기본 이벤트 루프로 설정
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

async def main():
    # 비동기 작업 수행
    await asyncio.sleep(1)
    print("Hello, uvloop!")

# 이벤트 루프 실행
asyncio.run(main())

주요 특징:

  1. 성능: uvloop는 Node.js의 libuv를 기반으로 하며, 기본 asyncio 이벤트 루프보다 2-4배 빠른 성능을 제공한다.
  2. 구현: Cython으로 작성되어 C 수준의 성능을 제공한다.
  3. 호환성: asyncio와 완벽하게 호환되며, 기존 asyncio 코드를 수정하지 않고도 사용할 수 있다.
  4. 플랫폼 지원: Linux, macOS, FreeBSD에서 사용 가능하지만, Windows에서는 지원되지 않는다.

uvloop가 제공하는 주요 성능 향상 요소:

  1. 향상된 I/O 처리:

    1
    2
    3
    4
    5
    6
    
    async def handle_connection(reader, writer):
        # uvloop는 이러한 I/O 작업을 매우 효율적으로 처리합니다
        data = await reader.read(100)
        writer.write(data)
        await writer.drain()
        writer.close()
    
  2. 타이머 최적화:

    1
    2
    3
    4
    5
    6
    
    async def timed_operation():
        # uvloop는 타이머 처리도 더 효율적으로 수행합니다
        start = asyncio.get_event_loop().time()
        await asyncio.sleep(0.1)
        end = asyncio.get_event_loop().time()
        return end - start
    
  3. 시스템 콜 최적화:

    1
    2
    3
    4
    5
    
    async def file_operations():
        # 파일 시스템 작업도 더 빠르게 처리됩니다
        async with aiofiles.open('file.txt', mode='r') as f:
            contents = await f.read()
        return contents
    

Httptools

httptools는 Node.js의 http-parser를 Python에 바인딩한 고성능 HTTP 파싱 라이브러리.
이는 Python의 기본 HTTP 파서보다 훨씬 빠른 성능을 제공한다.

httptools의 기본 사용법:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from httptools import HttpRequestParser

class RequestParser:
    def __init__(self):
        self.parser = HttpRequestParser(self)
        self.headers = {}
        self.body = bytearray()

    def on_header(self, name: bytes, value: bytes):
        # 헤더를 파싱할 때 호출됩니다
        self.headers[name.decode()] = value.decode()

    def on_body(self, body: bytes):
        # 요청 본문을 파싱할 때 호출됩니다
        self.body.extend(body)

httptools의 주요 특징과 장점들:

  1. 성능: Nginx의 HTTP 파서를 기반으로 하여 매우 빠른 파싱 속도를 제공한다.

  2. 구현: C로 작성되어 있어 높은 성능을 보장한다.

  3. 비동기 지원: 비동기 프로그래밍 모델과 잘 맞아 Uvicorn과 같은 비동기 서버에서 효과적으로 사용된다.

  4. 메모리 효율성: 스트리밍 파싱을 지원하여 메모리 사용을 최적화한다.

  5. 스트리밍 파싱:

    1
    2
    3
    4
    5
    6
    7
    
    class HTTPProtocol:
        def data_received(self, data):
            # 데이터가 도착하는 대로 점진적으로 파싱
            try:
                self.parser.feed_data(data)
            except Exception as exc:
                print(f"파싱 에러: {exc}")
    
  6. 고성능 URL 파싱:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    from httptools import parse_url
    
    def parse_request_url(url: bytes):
        parsed = parse_url(url)
        return {
            'schema': parsed.schema,
            'host': parsed.host,
            'port': parsed.port,
            'path': parsed.path,
            'query': parsed.query
        }
    

Uvloop와 Httptools의 통합

Uvicorn에서 이 두 구성요소가 어떻게 함께 작동하는지 살펴보자:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import uvicorn
from fastapi import FastAPI

app = FastAPI()

if __name__ == "__main__":
    uvicorn.run(
        app,
        host="0.0.0.0",
        port=8000,
        loop="uvloop",     # uvloop 사용
        http="httptools",  # httptools 사용
        workers=4          # 워커 프로세스 수
    )

이러한 구성요소들의 조합은 다음과 같은 이점을 제공한다:

  1. 향상된 처리량: uvloop와 httptools의 조합은 기존 Python 웹 서버보다 훨씬 높은 요청 처리량을 달성할 수 있다.
  2. 낮은 지연 시간: 최적화된 이벤트 루프와 HTTP 파싱으로 인해 각 요청의 처리 시간이 단축된다.
  3. 메모리 효율성: C 기반의 구현으로 인해 메모리 사용이 더 효율적이다.
  4. 안정성: 널리 검증된 libuv와 http-parser를 기반으로 하므로 안정적인 성능을 제공한다.
    이러한 특성들은 Uvicorn이 고성능 Python 웹 애플리케이션을 구축하는 데 이상적인 선택이 되게 한다.
    특히 FastAPI나 Starlette와 같은 현대적인 비동기 웹 프레임워크와 함께 사용할 때 그 장점이 더욱 두드러진다.

설치 및 사용

Uvicorn은 pip를 통해 쉽게 설치할 수 있다:

1
pip install uvicorn

기본 사용 예시:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import uvicorn

async def app(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!',
    })

if __name__ == "__main__":
    uvicorn.run("main:app", host="127.0.0.1", port=8000, log_level="info")

Uvicorn 환경 설정

다양한 설정 옵션

실제 운영 환경에서는 설정들을 서버의 특성과 요구사항에 맞게 조정해야 한다.
특히 다음과 같은 점들을 고려해야 한다:

  1. 서버의 하드웨어 리소스 (CPU, 메모리)에 맞춘 워커 수와 동시성 제한 설정
  2. 예상되는 트래픽 패턴에 따른 타임아웃과 버퍼 크기 조정
  3. 보안 요구사항에 맞는 SSL/TLS 설정
  4. 모니터링과 디버깅을 위한 적절한 로깅 레벨 설정
    이러한 설정들은 실제 부하 테스트를 통해 최적화되어야 하며, 운영 중에도 지속적인 모니터링과 튜닝이 필요하다.

uvicorn의 설정 옵션들을 체계적으로 설명해드리겠습니다. 각 카테고리별로 설정의 목적과 영향을 자세히 살펴보며, 실제 운영 환경에서 어떻게 활용할 수 있는지도 함께 알아보겠습니다.

서버 기본 설정

서버의 가장 기본적인 동작을 제어하는 핵심 설정.
이러한 설정들은 uvicorn이 어떻게 요청을 받고 처리할지를 결정한다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import uvicorn

config = {
    # 애플리케이션 설정
    "app": "main:app",          # 실행할 애플리케이션의 경로
    "host": "0.0.0.0",         # 바인딩할 호스트 주소
    "port": 8000,              # 리스닝할 포트 번호
    "uds": None,               # Unix 도메인 소켓 경로 (선택적)
    "fd": None,                # 파일 디스크립터 (선택적)
    "loop": "auto",            # 이벤트 루프 선택 (auto, asyncio, uvloop)
    "http": "auto",            # HTTP 프로토콜 구현체 (auto, h11, httptools)
    "ws": "auto",              # WebSocket 프로토콜 구현체 (auto, none, websockets)
    "lifespan": "auto",        # 수명 주기 이벤트 처리 (auto, on, off)
    "env_file": ".env"         # 환경 변수 파일 경로
}
최적화 설정

서버의 성능과 리소스 사용에 직접적인 영향을 미치는 설정.
이러한 설정들은 실제 운영 환경에서 매우 중요하다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
performance_config = {
    # 프로세스 설정
    "workers": 4,                    # 워커 프로세스 수
    "limit_concurrency": 1000,       # 동시 연결 제한
    "limit_max_requests": 10000,     # 워커 재시작 전 최대 요청 수
    "backlog": 2048,                 # 연결 대기열 크기
    
    # 타임아웃 설정
    "timeout_keep_alive": 5,         # keep-alive 연결 타임아웃 (초)
    "timeout_notify": 30,            # 종료 전 알림 대기 시간 (초)
    
    # 버퍼 설정
    "buffer_size": 16384,            # 요청/응답 버퍼 크기 (바이트)
}
보안 설정

서버의 보안을 강화하기 위한 설정.
SSL/TLS 설정과 프록시 관련 설정이 포함된다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
security_config = {
    # SSL/TLS 설정
    "ssl_keyfile": "/path/to/key.pem",      # SSL 키 파일 경로
    "ssl_certfile": "/path/to/cert.pem",    # SSL 인증서 파일 경로
    "ssl_keyfile_password": None,           # SSL 키 파일 암호
    "ssl_version": 2,                       # SSL/TLS 버전
    
    # 프록시 설정
    "proxy_headers": True,                  # X-Forwarded-* 헤더 처리
    "forwarded_allow_ips": "*",            # 신뢰할 수 있는 프록시 IP
}
로깅 설정

서버의 동작을 모니터링하고 문제를 진단하기 위한 로깅 관련 설정.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
logging_config = {
    # 로그 레벨 설정
    "log_level": "info",                   # 로그 레벨 (critical, error, warning, info, debug, trace)
    "access_log": True,                    # 접근 로그 활성화
    "use_colors": True,                    # 컬러 로그 사용
    
    # 로그 포맷 설정
    "log_config": {
        "version": 1,
        "disable_existing_loggers": False,
        "formatters": {
            "default": {
                "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
            }
        },
        "handlers": {
            "default": {
                "formatter": "default",
                "class": "logging.StreamHandler",
                "stream": "ext://sys.stdout"
            }
        },
        "loggers": {
            "uvicorn": {
                "handlers": ["default"],
                "level": "INFO"
            }
        }
    }
}
개발 설정

개발 환경에서 유용한 설정.
코드 변경 감지와 디버깅을 위한 옵션들이 포함된다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
development_config = {
    # 리로드 설정
    "reload": True,                        # 코드 변경 감지 및 자동 리로드
    "reload_dirs": ["app", "config"],      # 리로드를 감시할 디렉토리
    "reload_delay": 0.25,                  # 리로드 지연 시간 (초)
    
    # 디버깅 설정
    "debug": True,                         # 디버그 모드 활성화
    "log_level": "debug",                  # 상세한 로깅
}
리소스 관리 설정

서버의 리소스 사용을 제어하는 설정.
메모리와 CPU 사용량을 관리하는 데 도움이 된다.

1
2
3
4
5
6
7
8
resource_config = {
    # 프로세스 제한 설정
    "limit_max_requests": 10000,           # 워커 재시작 전 최대 요청 수
    "limit_concurrency": 1000,             # 동시 연결 제한
    
    # 메모리 관리
    "buffer_size": 16384,                  # 버퍼 크기 (바이트)
}

시스템 서비스 등록

Systemd 서비스 파일을 작성
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
[Unit]
Description=Uvicorn instance to serve FastAPI application
After=network.target
# 데이터베이스 의존성이 있다면 다음과 같이 추가할 수 있습니다
# Requires=postgresql.service
# After=postgresql.service

[Service]
# 서비스를 실행할 사용자와 그룹을 지정합니다
User=www-data
Group=www-data

# 작업 디렉토리를 지정합니다
WorkingDirectory=/path/to/your/fastapi/app

# 환경 변수 설정
Environment="PATH=/path/to/your/virtualenv/bin"
Environment="PYTHONPATH=/path/to/your/fastapi/app"
Environment="LOG_LEVEL=info"

# 실행 명령을 지정합니다
ExecStart=/path/to/your/virtualenv/bin/uvicorn \
    main:app \
    --host 0.0.0.0 \
    --port 8000 \
    --workers 4 \
    --loop uvloop \
    --http httptools \
    --log-level info \
    --access-log \
    --use-colors \
    --proxy-headers \
    --forwarded-allow-ips='*'

# 프로세스 관리 설정
Restart=always
RestartSec=10
StartLimitInterval=0

# 보안 관련 설정
PrivateTmp=true
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target
서비스 파일을 시스템에 등록하고 활성화하는 방법
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 서비스 파일 복사
sudo cp uvicorn.service /etc/systemd/system/

# systemd 데몬 리로드
sudo systemctl daemon-reload

# 서비스 시작
sudo systemctl start uvicorn

# 부팅 시 자동 시작 설정
sudo systemctl enable uvicorn

# 서비스 상태 확인
sudo systemctl status uvicorn

배포

프로덕션 환경에서는 Gunicorn과 함께 Uvicorn을 사용하는 것이 권장된다.
Gunicorn은 프로세스 관리와 로드 밸런싱을 제공하며, Uvicorn은 워커로 동작하여 높은 성능을 발휘한다.

예시

장점들을 살펴보면:

  1. Nginx는 리버스 프록시로서 SSL 종료, 정적 파일 서빙, 로드 밸런싱을 처리한다.
  2. Gunicorn은 프로세스 관리자로서 워커 프로세스의 생명주기를 관리한다.
  3. Uvicorn은 ASGI 서버로서 비동기 Python 애플리케이션을 효율적으로 실행한다.
    이 구성은 높은 성능과 안정성을 제공하며, 각 구성 요소의 장점을 최대한 활용할 수 있다.
    실제 운영 환경에서는 서버의 리소스와 트래픽 패턴에 따라 워커 수와 타임아웃 설정 등을 적절히 조정해야 한다.

서비스 아키텍처는 다음과 같다:
Nginx(리버스 프록시) → Gunicorn(프로세스 관리자) → Uvicorn(ASGI 서버) → FastAPI/Django 애플리케이션

  1. 먼저 Nginx 설정 파일을 작성한다.
    /etc/nginx/sites-available/fastapi_app:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    server {
        listen 80;
        server_name example.com;
    
        access_log /var/log/nginx/app-access.log;
        error_log /var/log/nginx/app-error.log;
    
        location / {
            proxy_pass http://unix:/run/gunicorn.sock;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
    
            # WebSocket 지원을 위한 설정
            proxy_read_timeout 300s;
            proxy_connect_timeout 75s;
        }
    }
    
  2. Gunicorn 설정 파일을 작성한다.
    `` /etc/gunicorn/conf.py`:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    # Gunicorn 설정 파일
    import multiprocessing
    from uvicorn.workers import UvicornWorker
    
    # 기본 설정
    bind = "unix:/run/gunicorn.sock"
    workers = multiprocessing.cpu_count() * 2 + 1
    worker_class = "uvicorn.workers.UvicornWorker"
    max_requests = 1000
    max_requests_jitter = 50
    
    # Uvicorn 워커 설정
    class CustomUvicornWorker(UvicornWorker):
        CONFIG_KWARGS = {
            "loop": "uvloop",
            "http": "httptools",
            "lifespan": "on",
            "access_log": True,
            "use_colors": False,
            "timeout_keep_alive": 5,
        }
    
    # 로깅 설정
    accesslog = "/var/log/gunicorn/access.log"
    errorlog = "/var/log/gunicorn/error.log"
    loglevel = "info"
    
  3. systemd 서비스 파일을 작성한다.
    `` /etc/systemd/system/gunicorn.service`:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    
    [Unit]
    Description=Gunicorn with Uvicorn workers for FastAPI application
    After=network.target
    Requires=nginx.service
    After=nginx.service
    
    [Service]
    User=www-data
    Group=www-data
    WorkingDirectory=/path/to/your/app
    Environment="PATH=/path/to/your/virtualenv/bin"
    Environment="PYTHONPATH=/path/to/your/app"
    Environment="PYTHONUNBUFFERED=1"
    
    # 환경 변수 파일 포함
    EnvironmentFile=/etc/default/gunicorn
    
    # Gunicorn 실행 명령
    ExecStart=/path/to/your/virtualenv/bin/gunicorn \
        --config /etc/gunicorn/conf.py \
        main:app
    
    # 프로세스 관리
    Restart=always
    RestartSec=10
    StartLimitInterval=0
    
    # 보안 설정
    NoNewPrivileges=true
    PrivateTmp=true
    ProtectSystem=full
    
    # 리소스 제한
    LimitNOFILE=65535
    TimeoutStopSec=5
    
    [Install]
    WantedBy=multi-user.target
    
  4. 환경 변수를 관리하기 위한 파일을 생성한다.
    /etc/default/gunicorn:

    1
    2
    3
    4
    5
    
    # Gunicorn 환경 변수
    PYTHONPATH=/path/to/your/app
    DATABASE_URL=postgresql://user:password@localhost/dbname
    REDIS_URL=redis://localhost:6379/0
    LOG_LEVEL=info
    
  5. 필요한 디렉토리와 권한을 설정한다

    1
    2
    3
    4
    5
    6
    
    # 로그 디렉토리 생성
    sudo mkdir -p /var/log/gunicorn
    sudo chown -R www-data:www-data /var/log/gunicorn
    
    # 소켓 디렉토리 권한 설정
    sudo chown www-data:www-data /run
    
  6. 서비스를 활성화하고 시작한다:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    # Nginx 설정 심볼릭 링크 생성
    sudo ln -s /etc/nginx/sites-available/fastapi_app /etc/nginx/sites-enabled/
    
    # Nginx 설정 테스트
    sudo nginx -t
    
    # systemd 데몬 리로드
    sudo systemctl daemon-reload
    
    # 서비스 시작
    sudo systemctl start gunicorn
    sudo systemctl start nginx
    
    # 부팅 시 자동 시작 설정
    sudo systemctl enable gunicorn
    sudo systemctl enable nginx
    

참고 및 출처

Uvicorn