Bài viết này tổng hợp các phương pháp cải thiện hiệu năng cho API server xử lý embedding dựa trên FastAPI + sentence-transformers, bằng cách sử dụng song song đa tiến trình và worker bất đồng bộ (asynchronous worker).

Tổng quan kiến trúc hệ thống

Server được xây dựng như sau:
  • Dùng FastAPI chạy trên uvicorn (ASGI)
  • Khi nhận /embed, nó:
  • Đưa dữ liệu vào asyncio.Queue
  • Worker nền (background EmbeddingWorker) lấy task
  • Tạo vector embedding bằng sentence-transformers
  • Lưu vector vào Qdrant (vector DB)

Các thành phần chính

  1. uvicorn --workers=N
  2. Khởi tạo N tiến trình OS level
  3. Mỗi tiến trình chứa:
+ App FastAPI
+ Queue riêng
+ Một instance embedding_worker
Kernel sẽ phân phối các request đều đặn giữa các tiến trình này (round-robin).
  1. EmbeddingWorker(worker_count=M)
Bên trong mỗi tiến trình FastAPI:
  • Tạo M tasks bất đồng bộ bằng asyncio.create_task()
  • Mỗi task lắng nghe và lấy job từ queue để xử lý song song
  • sentence-transformers model
  • Dùng PyTorch để tính embedding vector
  • Thiết lập device có thể là:
+ cpu
+ cuda (GPU NVIDIA)
+ mps (GPU Apple Silicon)

Mô tả song song hóa (Parallelism)

  • Process level: nhờ uvicorn --workers=N, tận dụng đa nhân CPU
  • Coroutine level: mỗi tiến trình có M worker bất đồng bộ
  • Queue: mỗi tiến trình có queue riêng → xử lý nhiều embedding song song
Ví dụ thực tế:
uvicorn main:app --workers 4
Trong khi đó trong code:
worker_count = 10
→ Có tổng 4 × 10 = 40 worker xử lý embedding chạy song song.

Các câu hỏi quan trọng về hiệu năng

Q1. Tăng số --workers có giúp nhanh hơn không?

✔️ Có — thường tăng số tiến trình giúp xử lý nhiều request đồng thời hơn.
Tuy nhiên, cũng cần cân nhắc:
  • CPU core thực tế
  • Tài nguyên RAM/GPU

Q2. Tăng worker_count trong EmbeddingWorker thì sao?

✔️ Có thể tăng throughput khi I/O chậm hoặc GPU chưa bị saturate.
Nhưng quá nhiều worker sẽ gây:
  • tranh chấp tài nguyên
  • overhead scheduler
→ Cần tìm điểm cân bằng phù hợp.

Q3. Queue size nhỏ có vấn đề gì?

✔️ Nếu queue quá nhỏ khi request tăng mạnh:
  • queue nhanh đầy
  • server trả lỗi 429 Too Many Requests
  • request bị delay
Giải pháp:
  • Tăng queue size vừa đủ
  • Hoặc thay queue nội bộ bằng queue ngoài như Redis

Q4. Phải làm sao nếu muốn chia sẻ queue giữa nhiều tiến trình?

→ Câu trả lời:
Dùng queue ngoại vi như Redis hoặc RabbitMQ.
Ví dụ Redis:
import redis

r = redis.Redis()

# enqueue
r.rpush("embedding_tasks", json.dumps({"text": "hello"}))

# dequeue (blocking pop)
task = json.loads(r.blpop("embedding_tasks")[1])

Hướng dẫn thử nghiệm hiệu năng

Bạn có thể thực hiện các thay đổi sau để đo hiệu năng:
📊 Thử với số uvicorn --workers khác nhau
  • workers = 1, 2, 4, 8
  • Đo thời gian xử lý request
  • So sánh throughput & latency
🧪 Check queue trong logs
  • Theo dõi queue size bằng htop hoặc logs custom
📈 Giám sát CPU/MPS
  • Trên macOS: Activity Monitor / powermetrics
  • Trên Linux: htop, nvidia-smi

Kết luận — Tóm tắt các chiến lược tuning

--workers=N: Tăng xử lý song song ở tiến trình
worker_count: Tăng concurrency trong mỗi tiến trình
Queue sizing: Cân bằng backlog & xử lý request
Shared queue (Redis): Hợp nhất task giữa nhiều tiến trình

Các chiến lược tuning được gợi ý

  • Đặt --workers phù hợp với số core CPU
  • Điều chỉnh worker_count dựa trên I/O hoặc GPU
  • Đặt queue size đủ lớn để giảm lỗi queue full
  • Ưu tiên sử dụng shared queue (Redis) khi cần scale-out
Nguồn bài viết - Dịch từ ryukato.github.io