Backend/Django

[django-rest-framework] (14) Throttling / Cache

dltjdn 2022. 2. 13. 18:24

Throttling이 필요한 이유

자신이 자신만의 API 서버를 만들고 클라이언트를 만들었을 때는 필요없을 수 있는 기능이다

그러나, 만약 오픈 API 서비스를 한다면 다른 개발자나 유저에 대해서 호출 횟수를 제한할 필요가 있다

 

기본 사용 용어

Rate : 지정 기간 내에 허용할 최대 호출 횟수 

Scope :  각 Rate에 대한 별칭

Throttle :  최대 호출 횟수를 넘은지를 판단하는 로직이 구현된 클래스

 

기본 제공 Throttle

AnonRateThrottle

  • 인증된 요청 :  제한X
  • 비인증된 요청 : IP 단위로 횟수 제한
  • 디폴트 scope : 'anon' → anon라는 이름으로 rate를 지정

UserRateThrottle

  • 인증된 요청 : 유저 단위로 횟수 제한
  • 비인증된 요청 : IP 단위로 횟수 제한
  • 디폴트 scope : 'user' → user라는 이름으로 rate를 지정

ScopedRateThrottle

  • 인증된 요청 : 유저 단위로 횟수 제한
  • 비인증된 요청 : IP 단위로 횟수 제한
  • 각 APIView내 throttle_scope 설정을 읽어, APIView별로 서로 다른 Scope 적용

 

Throttle 설정 예

# settings.py 에 기본 디폴트 설정
 REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES' : [],
    'DEFAULT_THROTTLE_RATES' : {
        'anon' : None,
        'user' : None,
    },
}

# (1) settings.py에서 디폴트 설정하기
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES' : [
        'rest_framework.throttling.UserRateThrottle',
    ],
    'DEFAULT_THROTTLE_RATES' : {
        'user' : '10/day',
    },
}


# (2) views.py에서 APIView에 설정하기
from rest_framework.throttling import UserRateThrottle

class PostViewSet(ViewSet):
    throttle_classes = userRateThrottle

 

 

API별 서로 다른 Rate 적용하기

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES' : [],
    'DEFAULT_THROTTLE_RATES' : {
        'contact' : '1000/day',
        'upload' : '20/day',
    },
}

# throttles.py
class ContactRateThrottle(UserRateThrottle):
    scope = 'contact'

class UploadRateThrottle(UserRateThrottle):
    scope = 'upload'


# views.py
class ContactListView(APIView):
    throttle_classes = [ContactRateThrottle]

class ContactDetailView(APIView):
    throttle_classes = [ContactRateThrottle]

class UploadView(APIView):
    throttle_classes = [UploadRateThrottle]

 

위의 코드를 아래와 같이 간결하게 구현할 수 있다

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES' : [
        'rest_framework.throttling.ScopedRateThrottle',
    ],
    'DEFAULT_THROTTLE_RATES' : {
        'contact' : '1000/day',
        'upload' : '20/day',
    },
}


# views.py
class ContactListView(APIView):
    throttle_scope = 'contact'

class ContactDetailView(APIView):
    throttle_scope = 'contact'

class UploadView(APIView):
    throttle_scope = 'upload'

 

Rates 포맷

숫자/간격 ( ex. 10/day )

( 숫자 : 지정 간격 내의 최대 요청 제한 횟수) 

( 간격 : 지정 문자열의 첫 글자만 사용  → "d", "day", "ddd" 모두 : Day /  "s" : 초 / "m" : 분 / "h" : 시 / "d" : 일 )

 

 

Rates 제한 매커니즘

SingleRateThrottle에서는 요청한 시간의 timestamp를 list로 유지해 cahe 서버에 저장 

  디폴트 cache에 저장할 수도 있고 원한다면 throttle마다 저장될 캐시를 설정할 수 있음

 

매 요청시마다, 

cachd에서 timestamp list를 가져온 후 체크 범위 밖에 timestamp값들은 모두 버린다

timestamp list의 크기가 허용범위보다 클 경우, 요청을 거부한다

timestamp list의 크기가 허용범위보다 작을 경우, 현재 timestamp를 list에 추가하고, cache에 다시 저장한다

   캐시 서버의 성능이 중요!

 

 

최대 호출 횟수 제한 넘은 경우

429 Too Many Requests 응답

→   Retry-After 라는 헤더를 통해 다시 API 활용이 가능한 시점 알려준다

 

 

Cache

매 요청 시마다 cache에서는 timestamp list를 get/set  →  캐시 성능이 중요

SimpleRateThrottle에서는 아래와 같이 디폴트 캐시 설정

# throttling.py

from django.core.cache import cache as default_cache

class SimpleRateThrottle(BaseThrottle):
	cache = default_cache

 

장고는 다양한 Cache를 지원한다

  • Memcached 서버 지원 : 별도의 서버기 때문에 장고가 재시작되어도 남아있음 But, 메모리 캐시기 때문에 Memcached자체가 재시작 된다면 초기화됨
  • 데이터베이스 캐시
  • 파일 시스템 캐시 : 서버가 재시작이 되도 활용할 수 있지만, 서버가 분산되어 있다면 활용할 수 X
  • 로컬 메모리 캐시 ( django의 디폴트 캐시 ) : 서버가 재시작이 되면 초기화됨
  • 더미 캐시 : 실제로 캐시 수행 X

redis를 활용한 캐시

  • django-redis-cache : 메모리 캐시이고 디스크와 싱크를 맞추어 재시작 되어도 재시작 되어도 데이터가 남아있음

→  최근에는 Memcached와 django-redis-cache가 주로 사용된다. 각 캐시의 한계를 알고 자신이 구현하려는 서비스에 맞추서 캐시를 선택해야 한다

 

 

Throttle별 cache 설정

from django.core.cache import caches

class CustomAnonRateThrottle(AnonRateThrottle):
	cache = cashes['alternate']