# DRF Throttling(요청 제한) 완전 정복: 왜 필요하고, 어떻게 설정·적용·커스텀할까? nginx 리버스 프록시에서 `limit_req`로 “입구 컷”을 하는 건 분명 효과적입니다. 하지만 **뷰/액션별로 서로 다른 정책**(예: 로그인은 분당 5회, 업로드는 하루 20회, 조회 API는 사용자당 1000회)을 걸고 싶을 때는, 서버/인프라가 바뀔 수도 있는 설정에만 기대기 어렵죠. DRF의 throttling은 **애플리케이션 레벨에서 ‘엔드포인트 특성’에 맞춘 제한**을 만들 수 있다는 점에서 “반드시 알아두면 좋은 기본기”입니다. --- ## Throttling이 필요한 이유 {#sec-c97ae217f027} DRF throttling은 “권한(permission)”처럼 **요청을 허용할지 말지 결정**하지만, 차이는 **영구적(권한) vs 임시적(요청 빈도 제한)** 입니다. DRF 문서도 throttling을 “클라이언트가 API에 보낼 수 있는 요청 속도를 제어하는 임시 상태”로 설명합니다. 현실적인 필요성은 대략 이렇게 정리됩니다. * **남용/공격 완화**: 무차별 대입(로그인), 스팸성 요청, 크롤링, 단순 DoS 등 * **비용/리소스 보호**: 업로드, 외부 API 호출, 무거운 쿼리, 생성형 AI 호출 같은 “비싼” 엔드포인트 * **공정성(fair use)**: 특정 사용자/키가 자원을 독점하지 않게 * **정책의 코드화**: “이 API는 분당 N회” 같은 규칙을 인프라가 아니라 코드로 관리 그리고 중요한 포인트: throttling은 **전역(global)** 으로도 걸 수 있고, **특정 뷰/액션 단위로**도 걸 수 있습니다. (바로 여기서 nginx만으로는 부족해지기 쉽습니다.) --- ## DRF Throttling이 동작하는 방식(핵심 개념만) {#sec-a5c59acab781} ### 1) Rate 문자열 {#sec-a2424ad14298} DRF는 보통 `"100/day"`, `"60/min"` 같은 형태로 제한을 설정합니다. ### 2) “누구를” 제한할 것인가(클라이언트 식별) {#sec-9cf8b4c551fc} * `UserRateThrottle`: 인증 사용자면 **user id** 기준, 비인증이면 **IP** 기준으로 제한 * `AnonRateThrottle`: **비인증(anonymous)** 요청만 IP 기준으로 제한 * `ScopedRateThrottle`: “이 뷰는 uploads 스코프” 같은 **scope 단위 정책**을 적용 IP 식별은 `X-Forwarded-For` 또는 `REMOTE_ADDR`를 사용하며, 프록시 뒤에 있다면 `NUM_PROXIES` 설정이 중요합니다. ### 3) 상태 저장은 Cache를 사용 {#sec-7b9c8a453729} DRF 기본 throttle 구현은 **Django cache backend**에 카운트를 저장합니다. 단일 프로세스/단일 서버면 기본 `LocMemCache`로도 동작하지만, 멀티 워커·멀티 레플리카 환경이면 Redis 같은 공유 캐시가 사실상 필수입니다. --- ## 전역(Global) 설정: 가장 빠른 시작 {#sec-c94dcae484bb} `settings.py`: ```python REST_FRAMEWORK = { "DEFAULT_THROTTLE_CLASSES": [ "rest_framework.throttling.AnonRateThrottle", "rest_framework.throttling.UserRateThrottle", ], "DEFAULT_THROTTLE_RATES": { "anon": "100/day", "user": "1000/day", }, } ``` 이렇게 하면 “기본 정책”이 전체 API에 적용됩니다. 요청이 제한되면 DRF는 기본적으로 **HTTP 429 (Too Many Requests)** 를 응답합니다. --- ## 뷰에 적용하는 법: 엔드포인트별로 다르게 걸기 {#sec-491e225aa51d} ### 1) 클래스 기반 뷰(APIView)에서 지정 {#sec-f32b57a3aba1} 전역 정책과 별개로 특정 뷰만 throttle을 다르게 줄 수 있습니다. ```python from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.throttling import UserRateThrottle class ExpensiveView(APIView): throttle_classes = [UserRateThrottle] def get(self, request): return Response({"ok": True}) ``` 문서에서도 `throttle_classes`로 뷰 단위 적용을 안내합니다. ### 2) 함수 기반 뷰(@api_view)에서 지정 {#sec-7a28800ffd4e} ```python from rest_framework.decorators import api_view, throttle_classes from rest_framework.throttling import UserRateThrottle from rest_framework.response import Response @api_view(["GET"]) @throttle_classes([UserRateThrottle]) def ping(request): return Response({"pong": True}) ``` ### 3) ViewSet의 특정 action에만 적용(@action) {#sec-f2285f7a57f9} “리스트 조회는 넉넉하게, 특정 POST 액션은 빡세게” 같은 정책에 유용합니다. ```python from rest_framework.decorators import action from rest_framework.throttling import UserRateThrottle from rest_framework.viewsets import ViewSet from rest_framework.response import Response class ItemViewSet(ViewSet): @action(detail=True, methods=["post"], throttle_classes=[UserRateThrottle]) def purchase(self, request, pk=None): return Response({"purchased": pk}) ``` 액션에 설정한 throttle은 viewset 레벨 설정보다 우선합니다. --- ## ScopedRateThrottle로 “뷰 특성별” 정책 만들기(강추) {#sec-69d1c4813e63} Scoped throttling은 “이 뷰는 uploads, 저 뷰는 login”처럼 **의미 있는 이름(scope)** 으로 정책을 분리할 수 있어 운영이 깔끔해집니다. `settings.py`: ```python REST_FRAMEWORK = { "DEFAULT_THROTTLE_CLASSES": [ "rest_framework.throttling.ScopedRateThrottle", ], "DEFAULT_THROTTLE_RATES": { "login": "5/min", "uploads": "20/day", "search": "60/min", }, } ``` 뷰에서 scope만 선언: ```python from rest_framework.views import APIView from rest_framework.response import Response class LoginView(APIView): throttle_scope = "login" def post(self, request): return Response({"ok": True}) ``` DRF는 `throttle_scope`가 있는 뷰에만 ScopedRateThrottle을 적용하고, **scope + 사용자 id 또는 IP**로 고유 키를 만들어 카운트합니다. --- ## 커스텀 Throttle 만들기: “키를 어떻게 잡을지”가 핵심 {#sec-892da9d3fb29} 내장 throttle로도 꽤 해결되지만, 실무에서는 이런 요구가 자주 나옵니다. * “로그인은 **IP + username** 조합으로 제한하고 싶다” * “API Key별로 제한하고 싶다” * “특정 헤더/테넌트/조직 단위로 제한하고 싶다” * “캐시를 default가 아니라 다른 캐시(예: redis cluster)로 쓰고 싶다” ### 1) 가장 흔한 방식: SimpleRateThrottle 상속 {#sec-759164135e01} `get_cache_key()`만 잘 정의하면 “무엇을 기준으로 제한할지”를 마음대로 바꿀 수 있습니다. ```python from rest_framework.throttling import SimpleRateThrottle class LoginBurstThrottle(SimpleRateThrottle): scope = "login" def get_cache_key(self, request, view): username = (request.data.get("username") or "").lower().strip() ident = self.get_ident(request) # IP 기반 식별(프록시 설정 영향) if not username: return None # username이 없으면 throttle 적용 안 함(원하면 다르게 처리) return f"throttle_login:{ident}:{username}" ``` 그리고 settings에 scope rate를 등록: ```python REST_FRAMEWORK = { "DEFAULT_THROTTLE_CLASSES": [ "path.to.LoginBurstThrottle", ], "DEFAULT_THROTTLE_RATES": { "login": "5/min", }, } ``` ### 2) 캐시를 다른 것으로 쓰고 싶다면(cache attribute) {#sec-99ae4b21e2b9} DRF 문서에 나온 것처럼, 커스텀 throttle에서 `cache`를 바꿀 수 있습니다. ```python from django.core.cache import caches from rest_framework.throttling import AnonRateThrottle class CustomCacheAnonThrottle(AnonRateThrottle): cache = caches["alternate"] ``` --- ## 배포하기 전 꼭 알아두자! {#sec-68bf2bd36ec0} ### 1) 프록시 환경에서 IP가 다 똑같이 잡히는 문제 {#sec-55e331e40267} IP 식별은 `X-Forwarded-For`/`REMOTE_ADDR` 기반입니다. 프록시 뒤라면 `NUM_PROXIES`를 정확히 설정하지 않으면 “모든 사용자가 한 명으로” 취급되는 사고가 납니다. ### 2) LocMemCache는 멀티 워커/멀티 서버에 약하다 {#sec-238e19948ccf} 캐시가 프로세스 로컬이면 워커마다 카운트가 따로 놀 수 있습니다. 운영에서는 Redis 같은 공유 캐시가 안전합니다(스로틀링이 “제대로” 동작하려면). ### 3) 동시성 경쟁 조건(race condition) {#sec-d87294622fb5} DRF 내장 구현은 고동시성에서 **몇 개 요청이 더 통과**할 수 있는 경쟁 조건 가능성이 있다고 문서에 명시돼 있습니다. 정말 “정확히 N회에서 끊어야” 하는 결제/쿠폰 같은 케이스면, 원자적 카운팅(예: Redis INCR + EXPIRE) 기반으로 커스텀 구현을 고려하세요. ### 4) 클라이언트 친화성: 429와 Retry-After {#sec-f405894aac21} DRF는 제한 시 기본적으로 429를 반환합니다. 또한 throttle의 `wait()`를 구현하면 `Retry-After` 헤더를 포함할 수 있습니다. --- ## 마무리: nginx와 DRF throttling, 둘 다 가져가자 {#sec-b08cdd5cec16} ![로봇클럽의 입장제한과 429 표식의 네온사인](/media/editor_temp/6/c96bb1d6-7cde-48a0-98aa-07396570391d.png) * nginx: 대량 트래픽/공격을 **가장 앞단에서** 깎아내는 방패 * DRF throttling: **엔드포인트의 의미와 비용**을 아는 애플리케이션 레벨에서 정교한 정책 적용 특히 “뷰 하나하나 특성에 맞는 제한”은 DRF throttling이 가장 손에 익고, 서버 환경이 바뀌어도 코드로 유지할 수 있어 강력합니다.