Subsections

21. Sample deployment configuration for a real-world website

This HowTo describes a sample deployment configuration that I believe is well adapted for most "real-world" websites. It is the configuration used by the http://www.cherrypy.org website itself.

21.1 Hardware

The website runs on the Celeron 1.3Ghz with 512MB RAM and shares the machine with about 30 other sites. The choice of the hardware depends mostly on your budget and how much traffic you plan to get...

21.2 Software environment

The site runs on Linux RedHat, but I believe it would run just as well on other OSes (including Windows). It runs on Python-2.3, but I believe it would run just as well on Python-2.2 or Python-2.1. However, Python-2.3 seems to be a bit faster than older versions.

21.3 CherryPy version

The site usually uses the latest CVS version from CherryPy (currently the 0.9-final) version. The 0.9 version seems to be much more stable than previous versions so if you are running an older version, I highly recommend that you upgrade to that version.

21.4 CherryPy server configuration

The site is deployed as a thread-pool server. I believe this is the best option for most websites. It is configured to run with 10 threads and since each page is really fast to build, the site should be able to support many (much more than 10) concurrent users...

The number of threads that you should use for your site depends on many criteria, including the number of concurrent users you plan to have, the average time your pages take to build and the power (CPU and RAM) of the machine that will run the site.

21.5 CherryPy server deployment

The site is running "exposed" on port 80, which means that there is no other webserver (like Apache) involved.

In order to listen on port 80, the server has to be started by the user "root". However, running the server as "root" is not very safe, so we use the special function initAfterBind to switch the user running the server to another user. The code is very simple:

def initAfterBind():
    import os
    # We must switch the group first
    os.setegid(500)  # Replace with desired user id.
    os.seteuid(2524) # Idem

Also, since the server runs a pool of threads, we must switch the user for all these threads as well (otherwise, only the parent thread will be switched). To do so, we just use the initThread special function, like this:

def initThread(threadIndex):
    import os
    # We must switch the group first
    os.setegid(500)  # Replace with desired user id.
    os.seteuid(2524) # Idem

21.6 Database configuration

The site uses MySql as a database backend to log the requests that are being made to the server. Therefore, each request triggers a database write.

Since the MySQLdb driver doesn't work well (from my experience) if we have several threads sharing the same database connection, each thread is given its own database connection, so it doesn't interfer with other threads.

This is done very easily using the initThread special function and the request special variable, like this:

def initThread(threadIndex):
    time.sleep(threadIndex * 0.2) # Sleep 0.2 seconds between each new database connection to make sure MySql can keep up ...
    request.connection = MySQLdb.connect('host', 'user', 'password', 'name')

This takes advantage of the fact that request is a special thread-aware class instance (so each thread can safely set/get member variables without have to worry about other threads)

Then, when we need to run a query, we just use code like this:

c=request.connection.cursor()
c.execute(query)
res=c.fetchall()
c.close()

And also, the forum on the website runs on top of ZODB and since ZODB doesn't support multiple threads by itself, I had to install ZEO so each thread also behaves as a ZEO client.

21.7 Sessions

The site also uses sessions (mostly for the forum, and also for one page in the online demo). Sessions are configured to store the data in RAM. Since the code for managing sessions is thread-safe, all threads have access to the same session data and everybody is happy :-)

I believe that other storage types for sessions (file, database, cookie) would work just as well.

21.8 Results

As I write these lines, the site has been running with no problem with this new configuration for about 20 days and I never had to restart it, proving that it is quite stable. Also, the RAM used by the threads has never increased, proving that there is no memory leaks ... It is true that the CherryPy.org website is not a "high-traffic" website, but it is still a good sign.

Note that if you're afraid that your CherryPy server might crash while you're sleeping, you can always set up a cronjob to check that the site is still running and to restart it if it isn't ...

See About this document... for information on suggesting changes.