Khi endpoint của bạn chỉ gọi hàm đồng bộ — ví dụ requests, SQLAlchemy đồng bộ hay xử lý CPU — hãy khai báo bằng def, FastAPI sẽ chạy nó trong threadpool và không chặn các request khác. Chỉ dùng async def khi bên trong có await thật sự, như gọi httpx.AsyncClient hay database bất đồng bộ. Quy tắc này áp dụng cho mọi endpoint.

Vấn đề

Nhiều người mới học FastAPI nghĩ “async lúc nào cũng nhanh hơn” nên khai báo mọi endpoint bằng async def, rồi bên trong gọi requests.get(), time.sleep() hoặc query SQLAlchemy đồng bộ. Một request chậm sẽ chặn toàn bộ event loop, các request khác phải xếp hàng chờ dù server vẫn còn thừa CPU — mà lỗi này rất khó phát hiện, vì chạy riêng lẻ vẫn đúng kết quả.

from fastapi import FastAPI
import requests
app = FastAPI()
@app.get("/users/{user_id}")
async def get_user(user_id: int):
# LỖI: requests.get() là hàm đồng bộ, chặn cả event loop
response = requests.get(f"https://api.example.com/users/{user_id}")
return response.json()

Trong lúc request này chờ API bên ngoài, không request nào khác được xử lý.

Giải pháp

Chia endpoint theo đúng bản chất của thư viện bên trong:

from fastapi import FastAPI
import requests
import httpx
app = FastAPI()
@app.get("/users/{user_id}")
def get_user(user_id: int):
# ĐÚNG: def thường -> FastAPI chạy trong threadpool
response = requests.get(f"https://api.example.com/users/{user_id}")
return response.json()
@app.get("/posts/{post_id}")
async def get_post(post_id: int):
# ĐÚNG: async def -> chỉ khi có await thật sự
async with httpx.AsyncClient() as client:
response = await client.get(f"https://api.example.com/posts/{post_id}")
return response.json()

FastAPI kiểm tra kiểu hàm khi đăng ký route: endpoint def được đẩy sang threadpool (mặc định 40 luồng), còn async def chạy trực tiếp trên event loop. Tôi đã đo thực tế trên uvicorn: 4 request đồng thời vào endpoint def dùng time.sleep(0.5) xong trong 0,56 giây; 2 request vào endpoint async def dùng time.sleep(0.5) mất 1,01 giây vì chúng chạy nối tiếp nhau; còn endpoint async def dùng await asyncio.sleep(0.5) thì 4 request xong trong 0,51 giây.

Quy tắc chọn nhanh:

  • Thư viện đồng bộ (requests, psycopg2, SQLAlchemy đồng bộ, đọc file, xử lý CPU) → dùng def.
  • Thư viện bất đồng bộ (httpx.AsyncClient, asyncpg, SQLAlchemy async, aiofiles) → dùng async def + await.
  • Dependency cũng tuân theo đúng quy tắc này.

Áp dụng vào dự án của bạn

Rà soát lại các endpoint async def trong dự án: tìm những chỗ gọi requests., time.sleep(, .query( hay .execute( đồng bộ mà không có await — đổi chúng sang def là xong, không cần sửa logic bên trong. Với endpoint mới, hãy quyết định theo thư viện: nếu thư viện có bản AsyncClient thì dùng async def, nếu không thì cứ dùng def. Đừng chuyển mọi thứ sang async chỉ vì nghe nói “async nhanh hơn” — async chỉ phát huy khi bạn thực sự await trong lúc chờ I/O.

Bài viết liên quan

Câu hỏi thường gặp

Dùng `def` thì FastAPI có chậm hơn `async def` không?

Không đáng kể với tác vụ I/O đồng bộ: endpoint def chạy trong threadpool nên vẫn xử lý nhiều request song song. Async chỉ vượt trội khi có nhiều kết nối cùng chờ I/O và bạn await đúng cách.

Endpoint `async def` lỡ gọi hàm đồng bộ thì sao?

Nó chặn event loop cho tới khi hàm đó xong, mọi request khác đứng chờ. Nếu không đổi được sang def, bọc hàm đó bằng await run_in_threadpool(...) từ fastapi.concurrency.

Endpoint `def` có gọi được code async không?

Không trực tiếp, vì def không dùng được await. Cách đơn giản nhất là chuyển endpoint sang async def; chỉ dùng asyncio.run() khi thật sự cần thiết vì nó tạo event loop mới.

Dependency có phải tuân theo quy tắc này không?

Có: dependency def chạy trong threadpool, dependency async def chạy trên event loop. Bài dependency yield quản lý DB session đã áp dụng đúng quy tắc này.

Làm sao phát hiện endpoint đang chặn event loop?

Dấu hiệu rõ nhất: một request chậm làm mọi endpoint khác chậm theo. Đo bằng middleware đo thời gian request, hoặc gửi nhiều request đồng thời bằng httpx rồi so sánh tổng thời gian.

💬 Bạn đã thử cách này trong dự án của mình chưa? Nếu gặp lỗi hoặc có cách làm gọn hơn, để lại bình luận bên dưới — mình đọc hết và sẽ bổ sung những ý hay vào bài.

Nguồn tham khảo