Accelerate Plone with Varnish

Introduction

This tutorial highlight the necessary steps to configure Varnish and lighttpd to serve your Plone site. The primary benefit is that you can tremendously improve performance of your Plone. In Bitubique.com case, the stock Plone installation was only able to push up to 13 requests per seconds, but putting Varnish and lighttpd in front push the performance up to 160 requests per seconds. That's 12 times faster than stock installation!

Please note that you don't need to use lighttpd if you only have one Plone site and does not have other website on the same machine, and does not plan to run virtual hosting or serves sites not running Plone.

Plone

Plone is powerful, advanced, and easy to use Content Management System (CMS) built on top of the free and powerful Zope application server. It is being used worldwide by organization such as NASA, Oxfam, eBay, Novell, Trolltech, Nokia, CIA, and more. You can get more information on Plone.org.

Varnish

Varnish is a HTTP accelerator, build from ground up with performance in mind. Varnish is targeted primarily at the FreeBSD 6/7 and Linux 2.6 platforms, and will take full advantage of the virtual memory system and advanced I/O features offered by these operating systems. You can get more information and the software from Varnish site.

lighttpd

lighttpd powers several huge sites such as YouTube, Wikipedia, and meebo among others. Its high speed io-infrastructure allows them to scale several times better with the same hardware than with alternative web-servers. Read more from lighttpd site.

Prerequisite

You need to have a working Plone installation with Virtual Host Monster installed. It won't be covered in this tutorial, but a little bit of Googling is all you need.

I'm using Ubuntu 7.10 (Gutsy Gibbon) as the base OS, but the steps outlined here should work with little or no modifications on any other Linux distributions.

Installation

Varnish

I'm using Varnish 1.1.2, the latest as of Dec 2007. You can get the source and binary, .rpm and .deb packages from Sourceforge. Follow the instructions and install using whatever methods suitable for your distribution.

Varnish is available in the Gutsy's package repository, but it is an old version, and I did encounter some stalled page issues using this version. So, I'd recommended to use the latest 1.1.2 version. You can use the following command sequence to to create .deb packages and use it to install.

$ sudo apt-get install pbuilder    # install pbuilder if you haven't done so
$ wget "http://downloads.sourceforge.net/varnish/varnish-1.1.2.tar.gz?modtime=1198163157&big_mirror=0" 
$ tar -xzf varnish-1.1.2.tar.gz 
$ cd varnish-1.1.2 
$ sudo /usr/lib/pbuilder/pbuilder-satisfydepends   # satisfy varnish build dependcies 
$ debuild -i -us -uc   # build .deb package 
$ cd .. 
$ sudo dpkg -i *.deb    # install .deb package

Create a new file in /etc/varnish/vcl.conf with the following contents:

# This is a basic vcl.conf file for varnish.
# Modifying this file should be where you store your modifications to
# varnish. Settnigs here will override defaults.

backend default {
	# Your Zope / Plone instance.
        set backend.host = "127.0.0.1";
        set backend.port = "8080";       
}

acl purge {
                "localhost";
}

sub vcl_recv {
        if (req.request != "GET" && req.request != "HEAD") {
                # PURGE request if zope asks nicely
                if (req.request == "PURGE") {
                        if (!client.ip ~ purge) {
                              error 405 "Not allowed.";
                }
                lookup;
                }
                pipe;
        }

        if (req.http.Expect) {
                pipe;
        }

        if (req.http.Authenticate || req.http.Authorization) {
                pass;
        }

        # We only care about the "__ac.*" cookies, used for authentication
        if (req.http.Cookie && req.http.Cookie ~ "__ac(|_(name|password|persistent))=") {
                pass;
        }

	# File type that we will always cache
        if (req.request == "GET" && req.url ~ "\.(gif|jpg|swf|css|js|png|jpg
|jpeg|gif|png|tiff|tif|svg|swf|ico|css|js|vsd|doc
|ppt|pps|xls|pdf|mp3|mp4|m4a|ogg|mov|avi|wmv|sxw|zip|gz|bz2|tgz|tar|rar|odc|odb
|odf|odg|odi|odp|ods|odt|sxc|sxd|sxi|sxw|dmg|torrent|deb|msi|iso|rpm)$") {
            lookup;
        }

        if (req.request == "POST") {
                pipe;
        }

        # force lookup even when cookies are present
        if (req.request == "GET" && req.http.cookie) {
                lookup;
        }
        lookup;
}

sub vcl_fetch {
        # force minimum ttl of 300 seconds
        if (obj.ttl < 300s) {
                set obj.ttl = 300s;
        }
}

# Do the PURGE thing
sub vcl_hit {
	if (req.request == "PURGE") {
		set obj.ttl = 0s;
		error 200 "Purged";
	}
}

sub vcl_miss {
	if (req.request == "PURGE") {
		error 404 "Not in cache";
	}
}

Of a particular interest, this configuration tell Varnish to always cache common static files (e.g. images), and don't invalidate the cached object for at least 5 minutes (300 seconds).

Next, modify the file /etc/default/varnish, and ensure its content is similar to this (especially the DAEMON_OPTS part):

# Configuration file for varnish
#
# /etc/init.d/varnish expects the variable $DAEMON_OPTS to be set from this
# shell script fragment.
#

# Maximum number of open files (for ulimit -n)
NFILES=131072

# Default varnish instance name is the local nodename.  Can be overridden with
# the -n switch, to have more instances on a single server.
INSTANCE=$(uname -n)

# Listen on port 6081, administration on localhost:6082.Use a fixed-size cache file.
DAEMON_OPTS="-a localhost:6081 \
             -T localhost:6082 \
             -f /etc/varnish/vcl.conf \
             -u varnish -g varnish \
             -s file,/var/lib/varnish/$INSTANCE/varnish_storage.bin,1G"

In this configuration, Varnish administration port is 6082, and the request is served from port 6081. Cache size is set at 1G. Please also ensure you have tuned your maximum open files limit.

Now start varnish with the following command:

sudo /etc/init.d/varnish restart

lighttpd

lighttpd is available from Gutsy's repository. Just launch the following commands to install it:

$ sudo apt-get install lighttpd

Now it's time to setup lighttpd to answer the actual queries from web-browser. Edit /etc/lighttpd/lighttpd.conf and replace the content with the following:

server.modules              = ( 
            "mod_access",
            "mod_alias",
            "mod_accesslog",
            "mod_compress",
            "mod_rewrite", 
            "mod_redirect", 
            "mod_proxy"
)

server.document-root       = "/var/www/"
server.errorlog            = "/var/log/lighttpd/error.log"
index-file.names           = ( "index.php", "index.html",
                               "index.htm", "default.htm",
                               "index.lighttpd.html" )
mimetype.use-xattr = "enable"
accesslog.filename         = "/var/log/lighttpd/access.log"
url.access-deny            = ( "~", ".inc" )
static-file.exclude-extensions = ( ".php", ".pl", ".fcgi" )
 
server.port               = 80
server.pid-file            = "/var/run/lighttpd.pid"
dir-listing.encoding        = "utf-8"
server.dir-listing          = "enable"
server.username            = "www-data"
server.groupname           = "www-data"
compress.cache-dir          = "/var/cache/lighttpd/compress/"
compress.filetype           = ("text/plain", "text/html", "application/x-javascript", "text/css")

#### external configuration files
## mimetype mapping
include_shell "/usr/share/lighttpd/create-mime.assign.pl"

## load enabled configuration files, 
## read /etc/lighttpd/conf-available/README first
include_shell "/usr/share/lighttpd/include-conf-enabled.pl"

url.rewrite-once = ( "^/(.*)$" => 
        "/VirtualHostBase/http/mysite.com:80/mysite_plone_instance/VirtualHostRoot/$1" )
proxy.server = ( "/VirtualHostBase/" => ( 
        ( "host" => "127.0.0.1" , "port" => 6081 ) ) 
)

Notice the last two directive (url.rewrite-once and proxy.server) is where the magic at, telling lighttpd to send to request for your Plone site via Varnish. Replace mysite.com with your actual domain name, and mysite_plone_instance with path to your Plone instance (check with ZMI). The proxy.server directive is telling lighttpd to send to request to Varnish.

Restart lighttpd:

$ sudo /etc/init.d/lighttpd restart

Run some benchmark and see the performance improvement and lower system overall load!

Post Installation

You can monitor Varnish statistics using varnishstat and varnishtop command.