Tập 12 đã giúp bạn tìm ra điểm nghẽn hiệu năng bằng slow query log và django-silk. Nhưng một API nhanh vẫn có thể sập nếu bị gọi dồn dập — client lỗi, bot quét endpoint, hay brute-force vào API đăng nhập. Tập này gắn rate limiting cho DRF bằng throttle có sẵn và django-ratelimit, để mỗi người dùng chỉ gọi API trong giới hạn cho phép.

1. Bật throttle mặc định của DRF

DRF có sẵn hai lớp throttle: AnonRateThrottle giới hạn theo IP cho khách chưa đăng nhập, UserRateThrottle giới hạn theo từng tài khoản. Chỉ cần khai báo trong settings.py là mọi view DRF được bảo vệ ngay. Dùng Redis làm cache backend (đã dựng ở tập 9) để bộ đếm chính xác khi chạy nhiều worker Gunicorn.

# settings.py
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES": [
"rest_framework.throttling.AnonRateThrottle",
"rest_framework.throttling.UserRateThrottle",
],
"DEFAULT_THROTTLE_RATES": {
"anon": "100/day",
"user": "1000/hour",
},
}

Với cấu hình trên, khách chưa đăng nhập gọi được 100 request mỗi ngày, mỗi tài khoản đăng nhập được 1.000 request mỗi giờ. Vượt giới hạn, DRF trả về lỗi 429 kèm header Retry-After cho biết bao lâu nữa mới được gọi lại.

2. Giới hạn riêng cho từng endpoint với ScopedRateThrottle

Không phải endpoint nào cũng nên chung một giới hạn: endpoint tạo giao dịch cần chặt hơn endpoint đọc danh sách, vì ghi dữ liệu tốn tài nguyên và dễ bị spam. ScopedRateThrottle đặt một mức riêng cho từng view.

# expenses/views.py
from rest_framework.throttling import ScopedRateThrottle
from rest_framework.viewsets import ModelViewSet
class ExpenseViewSet(ModelViewSet):
throttle_classes = [ScopedRateThrottle]
throttle_scope = "expense_create"
def create(self, request, *args, **kwargs):
return super().create(request, *args, **kwargs)
# settings.py — thêm vào DEFAULT_THROTTLE_RATES
"expense_create": "30/min",

3. Chống brute-force bằng django-ratelimit

Throttle của DRF không chặn được brute-force vào endpoint đăng nhập, vì kẻ tấn công đổi IP liên tục và mỗi IP vẫn nằm dưới giới hạn. django-ratelimit bổ sung lớp chặn theo IP, khóa tạm thời khi đăng nhập sai quá nhiều lần. Cài bằng pip install django-ratelimit, gắn decorator lên view đăng nhập.

# accounts/views.py
from django_ratelimit.decorators import ratelimit
from django.http import JsonResponse
@ratelimit(key="ip", rate="5/m", block=True)
def login_view(request):
# xử lý đăng nhập như bình thường
return JsonResponse({"detail": "Đăng nhập thành công"})

Khi bị chặn, view trả về 429 — ghi log kèm IP để Sentry (tập 7) cảnh báo. Giới hạn 5 lần mỗi phút đủ cho người dùng thật nhưng đủ chặt với kẻ dò mật khẩu.

4. Kiểm tra và theo dõi

Đừng tin cấu hình cho đến khi tự gọi thử: dùng curl gọi liên tiếp vào endpoint tạo giao dịch, đếm số lần trả về 429, hoặc viết test nhỏ assert 429 sau khi vượt giới hạn. Trên production, theo dõi tỉ lệ 429 trong log: nếu một IP thật bị chặn thường xuyên, hãy nới giới hạn hoặc chặn theo tài khoản.

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

Trong ứng dụng quản lý chi tiêu, hãy áp dụng ba lớp giới hạn ngay trong tuần này: throttle mặc định cho toàn bộ API, 30 request mỗi phút cho endpoint tạo giao dịch, django-ratelimit 5 lần mỗi phút cho API đăng nhập. Sau một tuần, xem log 429 để điều chỉnh: giới hạn đúng là giới hạn người dùng thật không bao giờ chạm tới, bot thì chạm ngay.

Bài viết liên quan

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

Rate limiting có làm chậm API cho người dùng bình thường không?

Không, nếu giới hạn đủ cao. Bộ đếm chỉ là một lần đọc cache Redis, tốn chưa tới một mili-giây. Hãy đặt giới hạn cao gấp 3–5 lần mức dùng cao nhất của người dùng thật, rồi hạ dần khi có số liệu.

Nên giới hạn theo IP hay theo tài khoản?

Theo tài khoản khi đã đăng nhập, vì một IP có thể là cả văn phòng dùng chung NAT. Chỉ giới hạn theo IP cho endpoint chưa đăng nhập như login hoặc đăng ký.

Bị trả về 429 thì client nên làm gì?

Đọc header Retry-After, chờ số giây rồi gọi lại kèm backoff tăng dần. Tuyệt đối không retry ngay, vì mỗi lần retry cũng tính vào giới hạn và kéo dài thời gian bị chặn.

Nguồn tham khảo