본문 바로가기
Django/Django REST framework

15. Throttling

by hyun-am 2022. 8. 25.

Throttling은 요청이 승인되어야 하는지 여부를 결정하는 점에서 permissions와 유사합니다. throttling은 임시 상태를 나타내며 클라이언트가 API에 대해 수행할 수 있는 요청 속도를 제어하는 데 사용됩니다.

 

permissions와 마찬가지로 여러 개의 throttles를 사용할 수 있습니다. API에는 인증되지 않은 요청에 대한 제한적인 조절 기능이 있고 인증된 요청에 대해서는 덜 제한적인 제한이 있을 수 있습니다.

 

여러 throttle을 사용하려는 또 다른 시나리오는 일부 서비스가 특히 리소스를 많이 사용하기 때문에 API의 다른 부분에서 서로 다른 제약 조건을 적용해야 하는 경우입니다.

 

버스트 조절 속도와 지속 조절 속도를 모두 적용하려는 경우에도 여러 조절을 사용할 수 있습니다. 예를 들어 사용자를 분당 최대 60개의 요청과 하루에 1000개의 요청으로 제한할 수 있습니다.

 

throttle이 반드시 속도 제한 요청만을 의미하는 것은 아닙니다. 예를 들어 스토리지 서비스는 대역폭에 따라 조절해야 할 수도 있고 유로 데이터 서비스는 액세스되는 특정 레코드의 수에 따라 조절하려고 할 수도 있습니다.

 

DRF가 제공하는 애플리케이션 레벨 조절은 무차별 대입 또는 서비스 거부 공격(denial-of-service attacks)에 대한 보안 조치 또는 보호로 간주되어서는 안됩니다. 고의적으로 악의적인 행위자들은 항상 IP 기원을 스푸핑할 수 있습니다. 이 외에도 내장 throttle 구현은 장고의 캐시 프레임워크를 사용하여 구현되며, 요청 속도를 결정하기 위해 비원자 연산을 사용하므로 때때로 fuzzy가 발생할 수 있습니다.

또한 DRF에서 제공하는 애플리케이션 레벨 조절은 서비스 남용에 대한 기본 보호 및 다양한 비즈니스 계층과 같은 정책을 구현하기 위한 것입니다.

Throttling이 결정되는 방법

permissions와 authentication이 마찬가지로 drf의 trhottling은 클래스 list로 정의됩니다.

view의 body를 실행하기 전에 list의 각 throttle을 확인합니다. throttle 검사가 실패하면 예외가 발생합니다. exceptions.Throttled가 발생하고 view의 본문이 실행되지 않습니다.

throttling 정책 세팅하기

기본적인 throttling 정책은 DEFAULT_THROTTLE_CLASSES 및 DEFAULT_THROTTLE_RATES 설정을 사용하여 전역적으로 설정할 수 있습니다. 예를들면 아래와 같습니다.

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle'
    ],
    'DEFAULT_THROTTLE_RATES': {
        'anon': '100/day',
        'user': '1000/day'
    }
}

DEFAULT_THROTTLE_RATES에 사용된 속도 설명은 throttle 기간으로 초, 분, 시간 또는 일을 포함할 수 있습니다.

APIVIEW CBV를 사용하여 view별 또는 viewset별로 조절 정책을 설정할 수도 있습니다.

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

class ExampleView(APIView):
    throttle_classes = [UserRateThrottle]

    def get(self, request, format=None):
        content = {
            'status': 'request was permitted'
        }
        return Response(content)

함수 기반 view와 함께 @api_view 데코레이터를 사용하는 경우 다음 데코레이터를 사용할 수 있습니다.

@api_view(['GET'])
@throttle_classes([UserRateThrottle])
def example_view(request, format=None):
    content = {
        'status': 'request was permitted'
    }
    return Response(content)

@action 데코레이터를 사용하여 생성된 경로에 대한 throttle 클래스를 설정할 수도 있습니다. 이 방식으로 설정된 throttle 클래스는 모든 viewset level 클래스 설정을 재정의 합니다.

@action(detail=True, methods=["post"], throttle_classes=[UserRateThrottle])
def example_adhoc_method(request, pk=None):
    content = {
        'status': 'request was permitted'
    }
    return Response(content)

How clients are identified

X-Forwarded_for HTTP 헤더 및 REMOTE_ADDR WSGI 변수는 조절을 위해 클라이언트 IP 주소를 고유하게 식별하는 데 사용됩니다. X-Forwarded-For 헤더가 있으면 헤더가 사용되며, 그렇지 않으면 WSGI 환경의 REMOTE_ADDR 변수값이 사용됩니다.

고유한 클라이언트 IP 주소를 엄격하게 식별해야 하는 경우 먼저 NUM_PROXIES 설정을 설정하여 API가 실행되는 응용 프로그램 프록시 수를 구성해야 합니다. 이 설정은 0이상의 정수여야 합니다. 0이 아닌 값으로 설정하면 애플리케이션 프록시 IP 주소가 먼저 제외되면 클라이언트 IP가 X-Forwarded-For 헤더의 마지막 IP 주소로 식별됩니다. 0으로 설정하면 REMOTE_ADDR 값이 항상 식별 IP 주소로 사용됩니다.

NUM_PROXIES 설정을 구성하면 고유한 NAT 게이트웨이 뒤에 있는 모든 클라이언트가 단일 클라이언트로 처리된다는 점을 이해하는 것이 중요합니다.

Setting up the cache

DRF에서 제공하는 throttle 클래스는 Django의 캐시 백엔드를 사용합니다. 적절한 캐시 설정을 지정했는지 확인해야 합니다. LocMemCache 백엔드의 기본값은 간단한 설정에 적합해야 합니다. 자세한 내용은 Django의 캐시 문서를 참고하면 되겠습니다.

‘기본값' 이외의 캐시를 사용해야 하는 경우 사용자 지정 throttle 클래스를 만들고 속성을 설정하여 사용할 수 있습니다. 예를들면 아래와 같습니다.

from django.core.cache import caches

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

‘DEFAULT_THROTTLE_CLASES’ 설정 키에서 또는 throttle_classes view 속성을 사용하여 사용자 지정 throttle class도 설정해야 합니다.

A note on concurrency(동시성에 대한 참고 사항)

기본 제공 throttle 구현은 경쟁 조건에 열려 있으므로 동시성이 높을 경우 몇가지 추가 요청을 허용할 수 있습니다.

프로젝트가 동시 요청 동안 요청 수를 보장하는 데 의존하는 경우 고유한 스로틀 클래스를 구현해야 합니다.

API Reference

AnonRateThrottle

AnonRateThrottle은 인증되지 않은 사용자만 제한합니다. 들어오는 요청의 IP 주소는 제한할 고유 키를 생성하는 데 사용됩니다.

허용된 요청 비율은 다음 중 하나에서 결정됩니다.(선호도 순)

  • AnonRateThrottle을 재정의하고 속성을 설정하여 제공할 수 있는 클래스의 rate 속성입니다.
  • DEFAULT_THROTTLE_RATES[’anon’] 설정입니다.

AnonRateThrottle은 알 수 없는 소스의 요청 비율을 제한하려는 경우에 적합합니다.

 

UserRateThrottle

UserRateThrottle은 API 전체에서 지정된 요청 비율로 사용자를 제한합니다. 사용자 ID는 제한할 고유키를 생성하는 데 사용됩니다. 인증되지 않은 요청은 수신 요청의 IP 주소를 사용하여 제한할 고유 키를 생성하는 것으로 대체됩니다.

허용된 요청 비율은 다음 중 하나에서 결정됩니다.(선호도 순)

  • UserRateThrottle을 재정의하고 속성을 설정하여 제공할 수 있는 클래스의 rate 속성입니다.
  • DEFAULT_THROTTLE_RATES[’user’] setting입니다.

API에는 동시에 여러 UserRateThrotte이 있을 수 있습니다. 그렇게 하려면 UserRateThrottle을 재정의하고 각 클래스에 대해 고유한 “범위"를 설정하십시오.

예를 들어 다음 클래스를 사용하여 여러 사용자 제한 속도를 구현할 수 있습니다.

class BurstRateThrottle(UserRateThrottle):
    scope = 'burst'

class SustainedRateThrottle(UserRateThrottle):
    scope = 'sustained'

그리고 다음 세팅을 따라야 합니다.

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': [
        'example.throttles.BurstRateThrottle',
        'example.throttles.SustainedRateThrottle'
    ],
    'DEFAULT_THROTTLE_RATES': {
        'burst': '60/min',
        'sustained': '1000/day'
    }
}

ScopedRateThrottle

ScopedRateThrottle 클래스를 사용하여 API의 특정 부분에 대한 액세스를 제한할 수 있습니다. 이 제한은 액세스 중인 view에 .throttle_scope 속성이 포함된 경우에만 적용됩니다. 고유한 throttle 키는 요청의 “범위"를 고유한 사용자 ID 또는 IP 주소와 연결하여 형성됩니다.

허용된 요청 비율은 요청 “scope”의 키를 사용하여 DEFAULT_THROTTLE_RATES 설정에 의해 결정됩니다.

예를들면 다음과 같습니다.

class ContactListView(APIView):
    throttle_scope = 'contacts'
    ...

class ContactDetailView(APIView):
    throttle_scope = 'contacts'
    ...

class UploadView(APIView):
    throttle_scope = 'uploads'
    ...

그리고 세팅은 다음과 같습니다.

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.ScopedRateThrottle',
    ],
    'DEFAULT_THROTTLE_RATES': {
        'contacts': '1000/day',
        'uploads': '20/day'
    }
}

ContactListView 또는 ContactDetailView에 대한 사용자 요청은 하루에 총 1000개의 요청으로 제한됩니다. uploadView에 대한 사용자 요청은 하루에 20개로 제한됩니다.

 

Custom throttles

커스텀 스로틀을 생성하려면 BaseThrottle을 재정의하고 .allow_request(self, request, view)를 구현하면 됩니다. 이 메서드는 요청이 허용되어야 하는 경우 True를 반환하고 그렇지 않으면 False를 반환해야 합니다.

선택적으로 .wait() 메서드를 재정의할 수도 있습니다. 구현된 경우 .wait()는 다음 요청을 시도하기 전에 권장되는 대기 시간(초)을 반환하거나 없음을 반환해야 합니다. .wait() 메서드는 .allow_request()가 이전에 False를 반환한 경우에만 호출됩니다.

.wait() 메서드가 구현되고 요청이 제한되면 Retry-After 헤더가 응답에 포함됩니다.

Example

다음은 10개의 요청마다 무작위로 1을 조절하는 속도 조절의 예시 입니다.

import random

class RandomRateThrottle(throttling.BaseThrottle):
    def allow_request(self, request, view):
        return random.randint(1, 10) !=  

 

 

참고링크

https://www.django-rest-framework.org/api-guide/throttling/

 

Throttling - Django REST framework

 

www.django-rest-framework.org

 

댓글