Belajar Django - Rate Limiting & Throttling
Episode 19 of 27

Belajar Django - Rate Limiting & Throttling

Melindungi API publik dari abuse dan brute-force dengan throttling DRF: throttle classes bawaan (per-user, per-IP), scope kustom, kombinasi multiple throttles, serta strategi rate limit untuk endpoint yang mahal.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Setelah hardening transport dan headers di episode 18, satu ancaman masih terbuka: abuse — scraping tak terbatas, brute-force login, atau spam ke endpoint yang mahal. Di episode ini kita memasang rate limiting & throttling: membatasi seberapa sering satu client bisa memanggil API dalam rentang waktu.

Mengapa topik ini penting? Karena tanpa throttling, satu aktor bisa menghabiskan resource kalian — CPU, query database, dan bandwidth — dengan sejuta request gratis. Rate limit bukan sekadar pengaman: ia juga pelindung bisnis, karena menutup penyalahgunaan yang bisa menjatuhkan layanan.

Throttle Classes DRF

DRF menyediakan throttle_classes per view. Tiga implementasi bawaan:

  • AnonRateThrottle — batas untuk user anonim (rate key anon).
  • UserRateThrottle — batas untuk user login (rate key user).
  • ScopedRateThrottle — batas berbasis scope yang dipasang per-view.
Pythonsettings.py - throttle rates
REST_FRAMEWORK = {
    "DEFAULT_THROTTLE_RATES": {
        "anon": "100/day",
        "user": "1000/hour",
        "burst": "20/min",
        "sustained": "200/hour",
    }
}

Format rate: <jumlah>/<periode> dengan periode second, minute, hour, day. Terapkan di view:

Pythonblog/views.py - ScopedRateThrottle
from rest_framework.throttling import AnonRateThrottle, ScopedRateThrottle
 
class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.filter(status="published")
    serializer_class = PostSerializer
    throttle_classes = [AnonRateThrottle, ScopedRateThrottle]
    throttle_scope = "sustained"
 
    @action(detail=False, methods=["post"], throttle_scope="burst")
    def search(self, request):
        ...

AnonRateThrottle selalu dipasang untuk membatasi anonim; ScopedRateThrottle memakai throttle_scope di level viewset dan bisa di-override per action. Endpoint search yang mahal mendapat scope burst yang lebih ketat.

Throttle Per-User dan Per-IP

UserRateThrottle dan AnonRateThrottle berbeda cara mengidentifikasi client:

  • UserRateThrottle memakai request.user — satu batas per akun, tidak peduli dari IP mana.
  • AnonRateThrottle memakai IP address (REMOTE_ADDR), karena anonim tidak punya identitas.

Mengapa perlu keduanya? Karena user yang sama bisa datang dari banyak IP (mobile, kantor), dan banyak anonim bisa berbagi satu IP (NAT kantor). Kombinasi keduanya:

PythonKombinasi throttle
from rest_framework.throttling import AnonRateThrottle, UserRateThrottle
 
class CommentCreateView(APIView):
    throttle_classes = [UserRateThrottle, AnonRateThrottle]
 
    def post(self, request):
        ...

Dengan kombinasi ini, batas terketat yang menang. DRF menyatukan beberapa throttle: request harus lolos semua throttle yang terpasang.

Tip

Default DRF memakai REMOTE_ADDR untuk anonim. Saat aplikasi di belakang Nginx/proxy (episode 23), semua anonim tampak berasal dari IP proxy! Konfigurasi NUM_PROXIES (via X-Forwarded-For) membuat Django menghitung IP asli. Lupa setting ini = satu anonim menghabiskan seluruh kuota anonim yang berbagi proxy yang sama.

Custom Throttle

Kebutuhan sering lebih spesifik dari bawaan: misal "maksimal 5 komentar per jam per user" memakai key unik. Buat custom throttle:

Pythonblog/throttles.py
from rest_framework.throttling import SimpleRateThrottle
 
class CommentPerHourThrottle(SimpleRateThrottle):
    scope = "comment_create"
 
    def get_cache_key(self, request, view):
        user = request.user
        if not user.is_authenticated:
            return None  # tangani anonim lewat throttle lain
        return self.cache_format % {
            "scope": self.scope,
            "ident": f"user:{user.pk}",
        }
Pythonsettings.py - daftarkan rate scope kustom
REST_FRAMEWORK = {
    "DEFAULT_THROTTLE_RATES": {
        "comment_create": "5/hour",
    }
}

get_cache_key menentukan identitas yang dihitung — di sini user:<pk>. Jika mengembalikan None, throttle di-skip untuk request itu. Implementasi SimpleRateThrottle menyimpan counter di cache backend — memakai Redis dari episode 12 sehingga shared antar worker (episode 24).

Response 429 dan Retry-After

Saat throttle tercapai, DRF mengembalikan status 429 Too Many Requests dengan header:

Respons throttled
HTTP/1.1 429 Too Many Requests
Retry-After: 45
Content-Type: application/json
 
{"detail": "Request was throttled. Expected available in 45 seconds."}

Client yang baik membaca Retry-After dan menunggu — pola backoff yang wajib dipahami untuk integrasi API. Test perilaku ini di kode:

Pythonblog/tests/test_throttling.py
from django.test import override_settings
from rest_framework import status
from rest_framework.test import APITestCase
 
@override_settings(
    REST_FRAMEWORK={
        "DEFAULT_THROTTLE_RATES": {"anon": "2/min"},
        "DEFAULT_THROTTLE_CLASSES": [
            "rest_framework.throttling.AnonRateThrottle",
        ],
    }
)
class AnonThrottleTests(APITestCase):
    def test_anon_limited(self):
        for _ in range(2):
            self.client.get("/api/posts/")
            self.assertEqual(self.client.get("/api/posts/").status_code,
                             status.HTTP_200_OK)
        self.assertEqual(self.client.get("/api/posts/").status_code,
                         status.HTTP_429_TOO_MANY_REQUESTS)

override_settings mengubah throttle rate hanya untuk durasi test — pola penting agar test tidak bergantung pada nilai produksi.

Melindungi Login dengan Throttling

Brute-force login adalah target klasik. Throttle di endpoint login (/api/token/ JWT dari episode 11):

Pythonaccounts/views.py - throttle login
from rest_framework.throttling import AnonRateThrottle
from rest_framework_simplejwt.views import TokenObtainPairView
 
class ThrottledTokenObtainPairView(TokenObtainPairView):
    throttle_classes = [AnonRateThrottle]
    throttle_scope = "login"
Pythonsettings.py - rate login
REST_FRAMEWORK = {
    "DEFAULT_THROTTLE_RATES": {
        "login": "5/min",
        "anon": "100/day",
        "user": "1000/hour",
    }
}

Batasan login: 5/min berarti satu IP hanya boleh mencoba 5 kali login per menit — brute-force menjadi sangat tidak efektif. Untuk pengamanan lebih dalam (lockout akun, 2FA), kita lanjut di episode 20.

Warning

Throttling bukan pengganti autentikasi yang benar — ia pengurang risiko. Kombinasikan dengan: password yang kuat, lockout setelah N kegagalan, dan monitoring alert saat 429 melonjak (observability episode 26). Dan selalu uji throttle di staging: rate yang terlalu agresif bisa memblokir user sah kalian sendiri.

Penutup

Inti yang harus dibawa pulang:

  • AnonRateThrottle (IP) + UserRateThrottle (user) + ScopedRateThrottle (per-view/scope).
  • Format rate 5/min, 200/hour; throttle menumpuk — request harus lolos semuanya.
  • Custom throttle via SimpleRateThrottle + get_cache_key untuk identitas khusus.
  • Backend cache (Redis) membuat throttle shared antar worker.
  • Respons 429 + Retry-After adalah kontrak yang dipahami client.
  • Throttle endpoint login untuk brute-force; kombinasikan dengan lockout & monitoring.

Di episode 20 selanjutnya kita menuntaskan lapisan keamanan auth: Auth Lanjutan & Permissions — JWT refresh dan rotasi, OAuth2 dengan django-oauth-toolkit, MFA 2FA, serta object-level permission untuk API SPA yang aman. Sampai jumpa di episode 20!

Belajar Django - Rate Limiting & Throttling | Belajar Django