DerekSchaefer.NET I do stuff, you read about it!

7Jun/110

Nginx + Django = Yay!

A couple weeks ago I replaced Apache with Nginx on all our servers hosting Django apps. The first thing I noticed was how much more simple the setup and configuration process was. The installation was easier, the configuration syntax is easier, and getting it to run in an existing Django environment was easier. So, pretty much everything was easier.

The next thing I noticed was the lowered memory footprint and CPU usage under load. I don't have hard numbers yet, but it is significantly lower. Further, and I only say this because I cannot substantiate it yet, it just feels faster. Based on other more technical reviews and comparison, I know this to be the case. Nevertheless, I plan on doing a more scientific evaluation of the situation, mainly regarding the number of requests per second that both can sustain on the same hardware and configuration.

Another piece of software that becomes useful when deploying Django on Nginx is FastCGI, installed conveniently in the python-flup package (sudo apt-get install python-flup). It includes a FastCGI server and requires essentially zero configuration. Although, for convenience, a control script like the one below will be useful.

Here is my shell script for managing the FastCGI server:

#!/bin/bash

CWD=$(cd `dirname $0` && pwd)

MYAPP=test_app
PIDFILE=/tmp/${MYAPP}_fcgi.pid
HOST=127.0.0.1
PORT=8080

# Associate it with the settings file
#SETTINGS=
# Use a socket instead of host/port
#SOCKET=
# Maximum requests for a child to service before expiring

METHOD=prefork
# Maximum number of children to have idle
MAXSPARE=5
# Minimum number of children to have idle
MINSPARE=5
# Maximum number of children to spawn
MAXCHILDREN=10
#MAXREQ=
# Spawning method - prefork or threaded

cd "`dirname $0`"

function failure () {
  STATUS=$?;
  echo; echo "Failed $1 (exit code ${STATUS}).";
  exit ${STATUS};
}

function start_server () {
  ./manage.py runfcgi pidfile=$PIDFILE \
    ${HOST:+host=$HOST} \
    ${PORT:+port=$PORT} \
    ${SOCKET:+socket=$SOCKET} \
    ${SETTINGS:+--settings=$SETTINGS} \
    ${MAXREQ:+maxrequests=$MAXREQ} \
    ${METHOD:+method=$METHOD} \
    ${MAXSPARE:+maxspare=$MAXSPARE} \
    ${MINSPARE:+minspare=$MINSPARE} \
    ${MAXCHILDREN:+maxchildren=$MAXCHILDREN} \
    ${DAEMONISE:+damonize=True}
}

function stop_server () {
  kill `cat $PIDFILE` || failure "stopping fcgi"
  rm $PIDFILE
}

DAEMONISE=$2

case "$1" in
  start)
    echo -n "Starting fcgi: "
    [ -e $PIDFILE ] && { echo "PID file exists."; exit; }
    start_server || failure "starting fcgi"
    echo "Done."
    ;;
  stop)
    echo -n "Stopping fcgi: "
    [ -e $PIDFILE ] || { echo "No PID file found."; exit; }
    stop_server
    echo "Done."
    ;;
  poll)
    [ -e $PIDFILE ] && exit;
    start_server || failure "starting fcgi"
    ;;
  restart)
    echo -n "Restarting fcgi: "
    [ -e $PIDFILE ] || { echo -n "No PID file found..."; }
    stop_server
    start_server || failure "restarting fcgi"
    echo "Done."
    ;;
  *)
    echo "Usage: $0 {start|stop|restart} [--daemonise]"
    ;;
esac

exit 0

And here is a basic Nginx config that hooks into FastCGI (so much more simple than Apache!):

server {
        listen 80 default;
        server_name localhost;

        access_log /var/log/nginx/localhost.access.log;

        location / {
                root /var/www/nginx-default;
                index index.html index.htm;
        }

        location /django {
                fastcgi_pass 127.0.0.1:8080;
                fastcgi_param PATH_INFO $fastcgi_script_name;
                fastcgi_param REQUEST_METHOD $request_method;
                fastcgi_param QUERY_STRING $query_string;
                fastcgi_param CONTENT_TYPE $content_type;
                fastcgi_param CONTENT_LENGTH $content_length;
                fastcgi_param REMOTE_ADDR $remote_addr;
                fastcgi_param SERVER_PORT $server_port;
                fastcgi_param SERVER_PROTOCOL $server_protocol;
                fastcgi_pass_header Authorization;
                fastcgi_intercept_errors off;
        }

        location /site_media/ {
                alias /home/ubuntu/cbot/media/;
                access_log off;
                expires modified +24h;
        }

        location /admin_media/ {
                alias /usr/share/pyshared/django/contrib/admin/media/;
                access_log off;
                expires modified +24h;
        }
}