Squeezing every drop of performance out of a Django app on Heroku

1. Build a site that doesn’t have users

I am only half joking. If you can avoid it, don’t have user logins. Your life is much easier if pages are fully cacheable.

2. Use Cloudflare

You’ve likely heard of this one. Cloudflare is free and does several useful things by default:

  • Automatically serves static content from a CDN
  • Terminates SSL
  • Deflects DDoS attacks

3. Serve static assets with WhiteNoise and Cloudflare

If you read the Heroku instructions carefully enough, you’ll already be using WhiteNoise. Combined with Cloudflare, this will serve your static assets efficiently.

4. Use asynchronous workers

By default, Gunicorn forks the Python process to serve concurrent requests. In a Heroku dyno’s 512MB of memory, you can serve around 3 concurrent requests.

5. Pool database connections on the workers

Once you’ve enabled Gevent, you’ll quickly find that each Gevent worker opens its own database connection and you’ll hit the connection limit on your cheap hobby Heroku Postgres instance.

Benchmarks

So how much does this improve things? This benchmark tests the improvement from the asynchronous workers and database pool. It is run on my laptop, but the results would be similar on Heroku.

Stock Gunicorn

Command:

gunicorn arxiv_vanity.wsgi --workers 3
$ wrk -t12 -c400 -d30s --timeout 10s http://127.0.0.1:8000/benchmark/
Running 30s test @ http://127.0.0.1:8000/benchmark/
12 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 0.00us 0.00us 0.00us -nan%
Req/Sec 7.39 13.42 70.00 88.14%
103 requests in 30.06s, 82.97KB read
Socket errors: connect 0, read 11, write 0, timeout 103
Requests/sec: 3.43
Transfer/sec: 2.76KB

Asynchronous workers and django-db-geventpool

Command (with the django-db-geventpool config in gunicorn_config.py):

gunicorn arxiv_vanity.wsgi --workers 3 -k gevent --worker-connections 100 --config gunicorn_config.py
$ wrk -t12 -c400 -d30s --timeout 10s http://127.0.0.1:8000/benchmark/
Running 30s test @ http://127.0.0.1:8000/benchmark/
12 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 545.22ms 473.38ms 1.41s 54.79%
Req/Sec 53.44 33.92 217.00 68.37%
16245 requests in 30.10s, 12.70MB read
Requests/sec: 539.78
Transfer/sec: 432.00KB

That’s it

That’s how we managed to serve that traffic on a shoestring budget. About 50 cents over 2 days, by my calculation.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Ben Firshman

Ben Firshman

Building tools for academics. Previously worked at Docker and The Guardian. https://fir.sh