Hướng dẫn sử dụng Granian trong Python: Web Server ASGI viết bằng Rust thay thế Uvicorn

Python tutorial - IT technology blog
Python tutorial - IT technology blog

Bối cảnh: Khi Uvicorn bắt đầu hụt hơi

Khoảng nửa năm trước, cụm API Core bên mình liên tục quá tải vào khung giờ 11h-13h. Stack lúc đó rất phổ biến: FastAPI đặt sau Uvicorn worker, quản lý bởi Gunicorn trên Kubernetes. Lúc traffic tăng đột biến lên 12.000 requests/giây, hệ thống bắt đầu rung lắc. CPU trên các pod vọt lên 90-95%, còn p99 latency nhảy từ 35ms lên tận 420ms.

Nút thắt cổ chai không nằm ở database hay query. Vấn đề nằm ngay tầng runtime HTTP server của Python. Dù FastAPI hay uvloop tối ưu rất tốt, Python vẫn phải gánh toàn bộ khâu parse HTTP, quản lý socket và điều phối event loop. Càng nhiều kết nối đồng thời, GIL và overhead của Python càng bộc lộ điểm yếu.

Đó là lý do tụi mình thử nghiệm Granian và quyết định đưa thẳng lên production.

Granian là HTTP server cho Python được viết hoàn toàn bằng Rust, xây dựng trên hai thư viện mạng nổi tiếng: hyper và tokio. Kiến trúc này giải phóng hoàn toàn Python khỏi gánh nặng I/O. Toàn bộ tác vụ nhận kết nối TCP, parse HTTP/1, HTTP/2 và TLS termination đều do tầng Rust native đảm nhiệm. Python app chỉ nhận request payload đã parse sẵn để chạy đúng logic nghiệp vụ.

Kết quả mang lại rất rõ ràng. Cùng cấu hình pod 2 vCPU và 2GB RAM, việc chuyển sang Granian giúp ứng dụng chịu tải cao gấp đôi. Độ trễ p99 hạ xuống dưới 80ms, đồng thời mức tiêu thụ RAM ổn định đáng kinh ngạc.

Cài đặt Granian

Granian được build sẵn dạng pre-compiled binary wheel trên PyPI. Bạn không cần cài đặt cargo hay rustc lên máy chủ mà vẫn dùng được ngay.

# Cài đặt qua pip
pip install granian

# Dùng Poetry
poetry add granian

# Dùng uv (khuyên dùng vì tốc độ tải wheel rất nhanh)
uv pip install granian

Kiểm tra binary sau khi cài đặt:

granian --version

Tạo một file main.py đơn giản bằng FastAPI để test cấu hình:

from fastapi import FastAPI

app = FastAPI(title="Granian Performance Demo")

@app.get("/")
async def root():
    return {"status": "ok", "server": "Granian", "engine": "Rust Tokio"}

@app.get("/compute")
async def compute():
    # Giả lập xử lý nhẹ
    total = sum(i * 2 for i in range(1000))
    return {"result": total}

Cấu hình chi tiết cho môi trường Production

Granian hỗ trợ 3 interface: ASGI (FastAPI, Starlette), WSGI (Flask, Django) và RSGI (chuẩn interface do tác giả Granian thiết kế riêng để đạt throughput tối đa). Với hầu hết dự án hiện tại, ASGI vẫn là lựa chọn an toàn và tương thích tốt nhất.

1. Khởi chạy cơ bản

granian --interface asgi main:app --host 0.0.0.0 --port 8000

2. Cấu hình Worker và Thread tối ưu

Granian kết hợp giữa workers (tiến trình độc lập) và threads (Tokio runtime threads trong mỗi worker). Đây là lợi thế lớn so với kiến trúc single-thread loop của Uvicorn:

granian --interface asgi main:app \
    --host 0.0.0.0 \
    --port 8000 \
    --workers 2 \
    --threads 2 \
    --backlog 4096 \
    --http 1 \
    --opt

Giải thích các tham số cốt lõi:

  • --workers: Số process worker độc lập. Trong môi trường container Kubernetes (thường cấp 2 vCPU), con số lý tưởng là 2. Tránh đặt quá nhiều worker gây tranh chấp CPU context switch.
  • --threads: Số luồng Tokio trong mỗi worker. Các luồng này xử lý I/O mạng song song ở tầng Rust mà không bị vướng Python GIL.
  • --backlog 4096: Độ dài hàng đợi TCP. Thiết lập này giúp hứng trọn các đợt traffic spike ngắn hạn mà không drop kết nối của client.
  • --opt: Bật cờ tối ưu hóa chuyển đổi dữ liệu nội bộ giữa Rust PyO3 và Python runtime.
  • --http 1: Ép dùng HTTP/1.1 nếu ứng dụng đứng sau Nginx/Ingress Controller, giúp giảm overhead thương thảo giao thức.

3. Đóng gói Dockerfile chuẩn Production

Dưới đây là Dockerfile thực tế gọn nhẹ, tối ưu layer caching mà bạn có thể áp dụng ngay:

FROM python:3.11-slim as base

ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PIP_NO_CACHE_DIR=off

WORKDIR /app

# Cài đặt curl để phục vụ container healthcheck
RUN apt-get update && apt-get install -y --no-install-recommends curl \
    && rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 8000

# Cấu hình 2 workers, 2 threads và tắt websocket nếu không dùng
CMD ["granian", "--interface", "asgi", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2", "--threads", "2", "--no-ws"]

Cờ --no-ws sẽ tắt hẳn subsystem WebSocket. Nếu API chỉ phục vụ REST thông thường, việc này giúp tiết kiệm thêm tài nguyên RAM và chu kỳ CPU cho mỗi kết nối.

Benchmark & Đánh giá thực tế

Đừng vội tin vào benchmark trên mạng. Hãy luôn tự đo đạc trên chính hạ tầng của bạn trước khi cutover traffic.

1. Đo throughput bằng wrk

Hãy chạy wrk từ một node khác cùng mạng nội bộ. Tránh chạy benchmark ngay trên máy chủ web vì tool test sẽ cạnh tranh CPU trực tiếp với server:

# Chạy 30 giây với 8 thread và 200 kết nối đồng thời qua IP nội bộ
wrk -t8 -c200 -d30s http://10.0.1.50:8000/compute

Trong kịch bản test trên endpoint /compute giả lập tải nhẹ, Granian đạt khoảng 28.500 RPS so với 16.200 RPS của cụm Gunicorn + Uvicorn worker (tăng ~75%). Điểm ấn tượng nhất là latency distribution: đường biểu diễn p95 và p99 của Granian rất phẳng, không xuất hiện các đỉnh nhọn do nghẽn event loop.

2. Giám sát bộ nhớ

Kiểm tra trực tiếp mức tiêu thụ RAM của các worker:

# Xem chi tiết tiến trình
ps aux | grep granian

# Theo dõi tài nguyên realtime qua top
top -p $(pgrep -d',' -f granian)

Thực tế vận hành cho thấy bộ nhớ của mỗi worker Granian duy trì ở mức 55-70MB lúc tải nhàn rỗi. Khi đẩy tải lên 5.000 RPS, con số này chỉ tăng lên khoảng 95MB rồi chững lại. Không hề có dấu hiệu rò rỉ bộ nhớ (memory leak) ở tầng Rust.

3. Quản lý Access Log

Ghi log truy cập đồng bộ ra console ở mức tải cao có thể bóp nghẹt I/O của container. Trên production, bạn nên tắt access log mặc định và chỉ bật khi cần debug:

# Chạy ở production: log level warning và tắt access log để tối ưu I/O
granian --interface asgi main:app \
    --host 0.0.0.0 \
    --port 8000 \
    --log-level warning \
    --no-access-log

Granian là một giải pháp nâng cấp đáng giá cho các hệ thống Python ASGI hiện đại. Bạn nhận được hiệu năng của Rust mà không phải viết lại bất kỳ dòng code Python nào.

Share: