DRF Throttling에서 “scope” 쓰는 두 가지 방식 비교: ScopedRateThrottle vs UserRateThrottle 상속

DRF에서 scope 기반 throttling을 걸 때 자주 나오는 두 패턴이 있습니다.

  • 방식 A: UserRateThrottle(또는 SimpleRateThrottle)을 상속해 scope가 박힌 커스텀 클래스를 만들고, 뷰에 throttle_classes로 직접 붙인다.
  • 방식 B: DEFAULT_THROTTLE_CLASSESScopedRateThrottle을 등록해두고, 뷰에는 throttle_scope = "login"만 적는다.

질문처럼 “결국 동일한 효과 아닌가?”에 대한 답부터 하면:

단일 속도 제한을 ‘유저ID(또는 비로그인 IP) + scope’ 기준으로 걸겠다는 수준에서는, 결과가 거의 동일합니다. 다만 적용 방식(실수 방지), 확장성(복수 정책/커스텀 기준), 코드 구조(설정 중심 vs 코드 중심)에서 차이가 나서 선택 기준이 생깁니다.


공통점: 둘 다 “scope + (user id or IP)”로 키를 만든다



  • ScopedRateThrottle은 뷰에 throttle_scope가 있으면 scope + 유저ID/IP로 키를 구성하고, DEFAULT_THROTTLE_RATES에서 해당 scope rate를 찾아 적용합니다.
  • UserRateThrottle도 기본적으로 인증 유저는 user id, 비인증은 IP를 기준으로 제한하며, 여러 개를 쓰려면 클래스를 상속해 scope를 다르게 두는 형태를 DRF가 공식 예시로 제시합니다.

차이 1) “어디에 scope를 적느냐”: 뷰 속성 vs 클래스 속성

방식 A: 클래스에 scope를 고정(명시적)

from rest_framework.throttling import UserRateThrottle
from rest_framework.views import APIView

class CustomDomainSaveThrottle(UserRateThrottle):
    scope = "custom_domain_save"

class SaveCustomDomainView(APIView):
    throttle_classes = [CustomDomainSaveThrottle]
  • scope가 클래스에 고정됩니다.
  • 뷰는 “이 클래스 쓰겠다”만 선언하면 끝.
  • 실수로 scope 문자열을 뷰마다 오타내는 케이스가 줄어듭니다(클래스명이 사실상 문서 역할).

방식 B: 뷰에 scope만 달고, 공용 throttle이 해석(설정 중심)

REST_FRAMEWORK = {
  "DEFAULT_THROTTLE_CLASSES": [
    "rest_framework.throttling.ScopedRateThrottle",
  ],
  "DEFAULT_THROTTLE_RATES": {
    "login": "5/min",
  }
}

from rest_framework.views import APIView

class LoginView(APIView):
    throttle_scope = "login"
  • scope가 뷰에 직접 들어갑니다.
  • 공통 throttle(ScopedRateThrottle)이 “이 뷰는 login 스코프네?” 하고 설정에서 rate를 찾아 씁니다.
  • 코드가 짧고, settings에서 정책을 한눈에 관리하기 좋습니다.

차이 2) “적용을 누가 보장하느냐”: 기본 등록 vs 뷰별 장착



방식 B의 포인트(오해가 많음)

DEFAULT_THROTTLE_CLASSESScopedRateThrottle을 넣어도, throttle_scope가 없는 뷰에는 적용되지 않습니다. 즉 “전역에 넣으면 전체 API가 다 막힐까?” 걱정은 보통 필요 없습니다.

반대로 방식 A의 강점

전역 설정을 건드리지 않고도, 특정 뷰에만 원하는 throttle을 “장착”할 수 있습니다. (팀/서비스 구조상 settings를 크게 못 만지는 경우나, 특정 앱/엔드포인트만 강제하고 싶을 때 편합니다.)


차이 3) 확장성: “단일 제한”을 넘어가면 A가 유리해진다

둘이 거의 같아 보이는 구간은 “한 뷰에 한 스코프, 한 속도 제한”까지입니다. 그 다음부터는 차이가 벌어집니다.

1) “버스트 + 지속” 같이 복수 throttle을 한 뷰에 걸고 싶을 때

DRF 문서도 여러 UserRateThrottle을 동시에 쓰는 패턴(버스트/서스테인)을 예시로 듭니다.

class Burst(UserRateThrottle):
    scope = "burst"

class Sustained(UserRateThrottle):
    scope = "sustained"

class SearchView(APIView):
    throttle_classes = [Burst, Sustained]

ScopedRateThrottle만으로는 “한 뷰에 스코프를 2개”를 자연스럽게 표현하기가 어렵고, 결국 클래스가 필요해집니다(또는 커스텀 구현).

2) “누구를 기준으로 제한할지”가 바뀌는 순간

ScopedRateThrottle은 기본적으로 user id / IP 축에서 움직입니다. 그런데 실무에서는 이런 요구가 금방 나옵니다.

  • 로그인: IP + username 조합으로 제한하고 싶다(계정 타겟 공격 대응)
  • 조직/테넌트: tenant_id 기준으로 묶어서 제한하고 싶다
  • API Key: 키 단위로 제한하고 싶다

이건 결국 get_cache_key() 같은 로직 커스텀이 필요해져서 방식 A(상속/커스텀 클래스)로 자연스럽게 이동합니다. (DRF도 커스텀 throttle을 권장하는 지점이 이쪽입니다.)


API연결을 여는 2개의 열쇠

그래서 뭘 쓰면 좋을까?

대부분의 “뷰별 rate limit”이면 → 방식 B(ScopedRateThrottle) 추천

  • 코드 최소화
  • settings에서 정책 일괄 관리
  • 뷰에는 throttle_scope만 적으면 끝
  • scope 없는 뷰에는 적용 안 됨

아래 중 하나라도 해당하면 → 방식 A(상속/커스텀 클래스) 추천

  • 한 뷰에 버스트+지속복수 제한을 동시에 걸고 싶다
  • “user/IP”가 아닌 커스텀 기준(username+IP, tenant, API key 등)이 필요하다
  • 전역 설정을 건드리기 어렵고 특정 뷰에만 강제 장착하고 싶다
  • 클래스명으로 정책을 “문서화”하고 싶다(읽는 사람이 바로 의도를 파악)

관련 글: