So this is what happened – I built an url shortening service at work for internal use. It’s a very basic app – shortens urls and tracks clicks. Two models – URL
and URLVisit
. URL
model contains the full url, slug for the short url, created time etc. URLVisit
has information related to the click, like user IP, browser data, click time etc and a ForeignKey
to URL
as expected.
Two different apps were using this service, one from me, another from a different team. I kept the Web Browsable API so the developers from other teams can try it out easily and they were very happy about it. The only job of this app was url shortening so I didn’t bother building a different home page. When people requested the /
page on the domain, I would redirect them directly to /api/
.
Things were going really great initially. There was not very heavy load on the service. Roughly 50-100 requests per second. I would call that minimal load. The server also had decent hardware and was running on an EC2 instance from AWS. nginx was on the front while the app was run with uwsgi. Everything was so smooth until it happened. After a month and half, we started noticing very poor performance of the server. Sometimes it was taking up to 40 seconds to respond. I started investigating.
It took me some time to find out what actually happened. By the time it happened, we have shortened more than a million urls. So when someone was visiting /api/url-visit/
– the web browsable api was trying to render the html form. The form allows the user to choose one of the entries from the URL
model inside a select (dropdown). Rendering that page was causing usages of 100% cpu and blocking / slowing down other requests. It’s not really DRF’s fault. If I tried to load a million of entries into a select like that, it would crash the app too.
Even worse – remember I added a redirect from the home page, directly to the /api/
url? Search engines (bots) started crawling the urls. As a result the app became extremely slow and often unavailable to nginx. I initially thought, I could stop the search engine crawls by adding some robots.txt or simply by adding authentication to the API. But developers from other teams would still time to time visit the API to try out things and then make the app non responsive. So I did what I had to – I disabled the web browsable API and added a separate documentation demonstrating the use of the API with curl
, PHP and Python.
I added the following snippet in my production settings file to only enable JSONRenderer
for the API:
1 2 3 4 5 |
REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': ( 'rest_framework.renderers.JSONRenderer', ) } |
Things have become pretty smooth afterwards. I can still enjoy the nice HTML interface locally where there are much fewer items. While on my production servers, there is no web browsable APIs to cause any bottlenecks.
7 replies on “Django REST Framework: Remember to disable Web Browsable API in Production”
I encountered the same problem, however for debugging purposes it’s still handy sometimes to keep the browsable API, but just disable the HTML forms (you will still be able to post json):
And add in in your settings:
This is even nicer, thank you! 🙂
Nice article, thank you!
Thank you again. I use this blog post again and again. 🙂
Hello world
for the first time
in the world
of django rest framework
Maybe with pagination and filtering querysets for possible related values you would not have such a problem.
Maybe not. Because DRF web view loads all available options while creating a new object.