Optimising Django, Apache and mod_wsgi for a lightweight Ubuntu VPS

2010-02-01

UPDATE: I'm now using an nginx-based solution on my server - I serve all my static content via nginx, proxying to apache (using the worker mgm and mod-wsgi) for django requests, and a fastcgi daemon for php. I've experienced a huge increase in efficiency over serving everything via apache. The below is still relevant but I would recommend reading up on nginx configuration before worrying about what I've written here.

UPDATE 2: One thing I neglected to mention is mod_wsgi 2.0's inactivity-timeout parameter, which makes a huge difference if you're hosting lots of low traffic django sites - see the mod_wsgi docs.

UPDATE 3 Like everyone else, I'm now using Gunicorn to serve wsgi applications like Django, still fronted by nginx


Recently I've been playing around with a very lightweight (128MB RAM) Ubuntu VPS from Openhost. I had been running a few low-traffic PHP sites on there, but recently I've started using Django so I installed Apache's mod_wsgi to serve my django sites. This lead to some memory issues — here's what I did to overcome them.

My server setup:

  • Ubuntu Server 8.04, on a 128MB VPS
  • Package management via apt
  • Apache 2.2
  • Python 2.5
  • Django 1.0
  • Sqlite3

mod_wsgi VirtualHost configuration

After installing and enabling mod_wsgi, I added the following to each site's VirtualHost directive:

<VirtualHost>
    ...
    WSGIScriptAlias / /path-to-site/apache/django.wsgi
</VirtualHost>

This simply embeds the django instance into the existing apache processes, resulting in them growing to upwards of 30MB over time. Luckily, mod_wsgi can be run in "daemon mode", which means the django instance gets its own apache listener, and the standard apache listeners can focus just on serving static media. To enable daemon mode, I changed my VirtualHost as follows:

<VirtualHost>
    ...
    WSGIDaemonProcess mysite processes=1 threads=1 display-name=%{GROUP}
    WSGIProcessGroup mysite
    WSGIScriptAlias / /path-to-site/apache/django.wsgi
</VirtualHost>

Even with the threads and processes options set to 1, each of the 4 daemons (one per site) was using around 20MB - still not a sustainable level on a 128MB VPS. Next step was to try consolidating the sites into one daemon. This has the advantage that it only loads the various python libraries etc into memory once, rather than for every site. To achieve this, I added the following to my http.conf:

WSGIDaemonProcess django-shared processes=1 threads=1 display-name=%{GROUP}
WSGIProcessGroup django-shared

and set all my VirtualHosts to use this group, ie:

<VirtualHost>
    ...
    WSGIProcessGroup django-shared
    WSGIScriptAlias / /path-to-site/apache/django.wsgi
</VirtualHost>

This resulted in one mod_wsgi daemon using around 32MB of ram, and so far is the best I've managed.

Conclusions

For low-traffic sites, the fact that django is a long-running process is not really an advantage as it means the process will be using memory even if the site hasn't had a hit in days. However, the benefits of using django far outweigh the downsides for me.

I'd recommend playing around with the different config options — obviously for larger sites there will be advantages to having a daemon for each site. I'm yet to experience a traffic spike so I'm not sure how my configuration will perform under pressure.

It's worth looking into how you can optimise other processes on your server too. For me the biggest waste of memory was mysql (used on my php sites). Following Chris Johnston's guide I was able to reduce this dramatically, freeing up more memory for my webservers.