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']