Published

It wasn't long ago that I signed up for my first slice over at Slicehost, a cool VPS host out of St. Louis, Missouri. I needed a host for a Django application and Kyle told me that I shouldn't waste my time with anyone else.

Having the whole server to yourself is great, but it also means making sure you get everything right, from the firewall to the Web server, so I've had to do some homework. Specifically, Apache is a beast and needs to be shorn, especially if you are working with a server with 256MB of RAM. I don't claim to be an Apache expert, but here are some tips that have worked for me.

Prefork MPM Settings

Running Django, I've been using Apache's Prefork MPM, so that's what I'll address here. On Slicehost, Apache's defaults look like this:

<IfModule mpm_prefork_module>
    StartServers          5
    MinSpareServers       5
    MaxSpareServers      10
    MaxClients          150
    MaxRequestsPerChild   0
</IfModule>

There's a lot to explain here, and I won't cover it all. Feel free to check out Slicehost's concise take, as well as Apache's documentation.

The important thing to understand is that this section sets how Apache will spawn child servers, and how they will handle requests in turn. Multiple child servers allow us to serve multiple clients simultaneously. However, each child process consumes a certain amount of memory. If not properly configured, Apache may spawn enough child processes to exhaust real memory and begin swapping to disk (especially on small servers). The Apache documentation refers to this swapping as "thrashing"—an apt description as the server performance degrades dramatically.

Here's a look at the Apache processes on one of my 256MB slices:

$ ps o pid,rss,command -u www-data
  PID   RSS COMMAND
 3752 40560 /usr/sbin/apache2 -k start
 3754 39536 /usr/sbin/apache2 -k start
 3819  1752 nginx: worker process
 3820  1748 nginx: worker process
 3821  1380 nginx: worker process
 3822  1376 nginx: worker process

You'll notice that, along with Apache, I have four proxy nginx processes, a resource-saving tip I learned at punteney.com. That's a way of having a lightweight Web server take care of static pages (like images and stylesheets) and only having Apache do the heavy lifting of serving dynamic content. But let's stay focused on Apache for now.

You can see that I have two Apache processes running, occupying about 40 MB of memory each. This footprint will depend mostly on the type of Web application Apache is serving. On another site of mine, running a more basic Django app, Apache processes average 30.289 MB.

So, at 40 MB per process, I can have about six Apache children running before I completely exhaust my 256 MB of RAM. The trouble is, I have to run a few other processes, like nginx and my PostgreSQL database. I'd also like to have a little memory left over to dedicate to memcached to speed up my site.

Aside from Apache, the processes on my server take up about 54 MB of RAM, under little to no traffic load. This includes nginx, PostgreSQL, and some other lightweight utilities. Taking all these specifics into account, I set up Apache to start no more than four child processes:

    StartServers          2
    MinSpareServers       1
    MaxSpareServers       4
    MaxClients            4
    MaxRequestsPerChild   100

So, four times 41 is a 164 MB footprint for Apache. 164 plus 54 is 218 MB for all my necessary server processes except for memcached, leaving me about 40 MB of margin for caching and extra load. At the end of the day, for optimum performance, what I want to see is something like this:

$ free -m
             total       used       free
Mem:           256        245         10
-/+ buffers/cache:        198         57
Swap:          511          0        511

Specifically, zero memory used in swap.

Apache Modules

After doing some experimentation, I've come to the conclusion that the default Apache modules don't make a huge difference in the overall memory footprint. For instance, with the default set of Apache modules on Slicehost, my child processes were maximum 42.060 MB, minimum 40.780 MB in size. After going into /etc/apache2/mods-enabled/ and ruthlessly deleting any module symlinks that didn't cause my site to break, I ended up with process sizes varying from 40.736 to 41.580 MB in size. So, I might be saving a megabyte per process. Obviously the bulk of the memory overhead comes from what my app loads in mod_python.

Honestly, I'm still watching to see if all this theoretical talk works out in production. If I've omitted anything, feel free to comment.

Alternatively...

3 Responses to Apache Optimization

  1. marpada wrote on May 5, 2009 at 10:17

    Congratulations for this post, I've read lots of documents about Apache tunning but yours is one of the most down-to-earth and easy to understand I've found.

  2. Thanks dude, helped out alot, marpada posted this in the hwdmedia forum and consequently I've discovered this gem of a guide which is dummy proof for pepz like me.

  3. Patrick wrote on November 4, 2010 at 08:26

    You rock. Finally something useful to read. If you have other tips email me please!