04 Throttling
04 Throttling
Define throttling
In the Terminal
Clone the repo. Your command should look something like this:
What is Throttling?
No matter how we choose to host our Django application, we’re always
going to be resource-limited at some point. Even if you design amazing,
automatically-scaling infrastructure in the world’s best cloud platform, you
probably only have a finite amount of money.
For example, a client might have a burst rate of 60 requests per minute,
and 1,000 requests per day. Let’s say the client starts making two requests
per second. They would exhaust their burst quota after 30 seconds, and
then have to wait 30 seconds before they could start making requests again.
DRF throttles are implemented using throttle classes. Some base classes are
provided to take care of common use cases, and can be used as base classes
for custom throttling. DRF provides the
rest_framework.throttling.AnonRateThrottle class to define limits for
anonymous users, and rest_framework.throttling.UserRateThrottle for
logged-in users.
The throttling classes to use, as well as their limits, are defined using the
REST_FRAMEWORK dictionary in the Django settings file. The
DEFAULT_THROTTLE_CLASSES define a list of throttle classes to apply, and the
DEFAULT_THROTTLE_RATES define their limits.
REST_FRAMEWORK = {
# existing settings omitted
"DEFAULT_THROTTLE_CLASSES": [
"rest_framework.throttling.AnonRateThrottle",
"rest_framework.throttling.UserRateThrottle"
],
"DEFAULT_THROTTLE_RATES": {
"anon": "500/day",
"user": "2000/day"
}
}
Notice that the rates are set as a string, in the form of <n>/<period>. The
period can be second, minute, hour or day.
class AnonRateThrottle(SimpleRateThrottle):
scope = 'anon'
The scopes are arbitrary, you can come up with your own ones when
writing throttle classes. Let’s look at that now, as we need custom classes to
implement burst/sustained rate limiting.
Burst and Sustained Rates
class AnonSustainedThrottle(AnonRateThrottle):
scope = "anon_sustained"
class AnonBurstThrottle(AnonRateThrottle):
scope = "anon_burst"
class UserSustainedThrottle(UserRateThrottle):
scope = "user_sustained"
class UserBurstThrottle(UserRateThrottle):
scope = "user_burst"
Throttling file
In our case, these classes will all be in the blog/api/throttling.py file.
Then the settings to use these classes and scopes are like this:
REST_FRAMEWORK = {
# existing settings omitted
"DEFAULT_THROTTLE_CLASSES": [
"blog.api.throttling.AnonSustainedThrottle",
"blog.api.throttling.AnonBurstThrottle",
"blog.api.throttling.UserSustainedThrottle",
"blog.api.throttling.UserBurstThrottle",
],
"DEFAULT_THROTTLE_RATES": {
"anon_sustained": "500/day",
"anon_burst": "10/minute",
"user_sustained": "5000/day",
"user_burst": "100/minute",
},
}
Here, anonymous users are allowed 500 requests per day (sustained) or 10
per minute (burst). Logged-in users can do 5,000 requests per day
(sustained) or 100 per minute (burst).
Anonymous users
DRF anonymous users are determined by IP address, so multiple clients at
the same IP address would be considered the same “user”.
Throttles Per View and Scoped
Throttles
Here’s how we could scope our Blango API views. Let’s limit our
PostViewSet to 50 requests per minute, and UserDetail view to 2000
requests per day.
REST_FRAMEWORK = {
# existing settings omitted
"DEFAULT_THROTTLE_RATES": {
# existing settings omitted
"post_api": "50/minute",
"user_api": "2000/day"
},
}
class PostViewSet(viewsets.ModelViewSet):
throttle_classes = [ScopedRateThrottle]
throttle_scope = "post_api"
# existing attributes/methods omitted
class UserDetail(generics.RetrieveAPIView):
throttle_classes = [ScopedRateThrottle]
throttle_scope = "user_api"
# existing attributes/methods omitted
Try It Out
Let’s add some throttling to the Blango API. We’ll set up sustained and burst
limits for both authorized and anonymous users.
class AnonSustainedThrottle(AnonRateThrottle):
scope = "anon_sustained"
class AnonBurstThrottle(AnonRateThrottle):
scope = "anon_burst"
class UserSustainedThrottle(UserRateThrottle):
scope = "user_sustained"
class UserBurstThrottle(UserRateThrottle):
scope = "user_burst"
Now save the file and head over to settings.py. Add the
DEFAULT_THROTTLE_CLASSES and DEFAULT_THROTTLE_RATES keys to the
REST_FRAMEWORK dictionary:
Open settings.py
"DEFAULT_THROTTLE_CLASSES": [
"blog.api.throttling.AnonSustainedThrottle",
"blog.api.throttling.AnonBurstThrottle",
"blog.api.throttling.UserSustainedThrottle",
"blog.api.throttling.UserBurstThrottle",
],
"DEFAULT_THROTTLE_RATES": {
"anon_sustained": "500/day",
"anon_burst": "10/minute",
"user_sustained": "5000/day",
"user_burst": "100/minute",
},
The quickest way to check if throttling is working is with the DRF GUI, as an
anonymous user. Load up any DRF URL and refresh the page repeatedly.
On the 10th attempt, you’ll get a response like:
throttled response
View Blog
The response has a 429 Too Many Requests status, and sets the header
Retry-After, which is a number of seconds after which you can expect the
response to be successful. This is only an approximation, but it’s good
etiquette to not try the request again until after this amount of time has
elapsed. Of course, there’s nothing stopping you from attempting the
request again if you really want!
Custom Throttling
What if you want to write your own throttling rules? You can write your
own throttling classes and override the allow_request() method: return
True or False to allow or deny the request. Here’s an example class that the
DRF documentation provides, which will randomly deny one in every ten
requests:
import random
class RandomRateThrottle(throttling.BaseThrottle):
def allow_request(self, request, view):
return random.randint(1, 10) != 1
You can read the offical Custom throttles documentation to learn more.
That’s all for throttling in Django Rest Framework. Next we’ll look at adding
filtering to our API.
Pushing to GitHub
Pushing to GitHub
Before continuing, you must push your work to GitHub. In the terminal:
git add .
git commit -m "Finish throttling"
Push to GitHub:
git push