Movable Type 4.2 introduced (among other things) built-in pagination. When you have a set of entries published to a dynamic index, you can auto-paginate them with some magical tags and it works wonderfully. That is, it works unless you wanted to use Movable Type’s built-in caching system for dynamic content. Movable Type’s cache entries are unique to a given relative URL excluding the request parameters. In the case of pagination, request parameters make all the difference on what should be cached. If you’re not following, this means that the following URLs are identical as far as the MT cache is concerned:

/
/?limit=10&offset=10
/?whatever_its_all_the_same_to=MT

Normally, having a blog that doesn’t do any caching isn’t a huge deal, but as soon as you start to get some traffic, it can really destroy your server. Our bloggers have been begging for paginated indices for years now and deploying our redesigned blogs without pagination was just not an option, so I began my quest for great caching.

I ended up using apache’s built-in mod_disk_cache and a reverse proxy to get around mod_rewrite bugginess. mod_cache takes my caching woes right out of the loop because it provides a proper cache handle using the entire URI including GET parameters. Turning on caching makes all the static content cached instantly, which is not exactly what I’m going for. It misses the dynamic content because mod_cache stupidly steps all over itself with mod_rewrite. I got some advice on that in #apache@freenode from noodl who recommended setting up a reverse proxy to handle all the cache hits, then pass the misses to an internally accessible virtual host. That’s exactly what I did and it speeds up my environment roughly tenfold. Here’s an abridged version of my configuration:

CacheEnable disk /
ServerName myblogsite.com
ProxyRequests Off
Order deny,allow
Allow from all
ProxyPass / http://myblogsite.local/
ProxyPassReverse / http://myblogsite.local/
ErrorLog /var/log/apache2/error.log
LogLevel debug
CustomLog /var/log/apache2/access.log combined
DocumentRoot /var/www/myblogsite.com/
ServerName myblogsite.local
ScriptAlias /admin /usr/lib/cgi-bin/movabletype/mt.cgi
Alias /mt-static /usr/share/movabletype/static
AllowOverride All
ErrorLog /var/log/apache2/error.log
LogLevel warn
CustomLog /var/log/apache2/access.log combined

As a rough comparison, my requests per second (RPS) were stuck at around 0.5 until I got this cache working. Here’s the output of a simple test with apache bench:

stevecrozz@wxp-im-video:~$ ab -n 1000 -c 10 http://myblogsite.com/
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking myblogsite.com (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests
Server Software:        Apache/2.2.9
Server Hostname:        myblogsite.com
Server Port:            80
Document Path:          /
Document Length:        44954 bytes
Concurrency Level:      10
Time taken for tests:   68.658 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      45396832 bytes
HTML transferred:       44986422 bytes
Requests per second:    14.57 [#/sec] (mean)
Time per request:       686.575 [ms] (mean)
Time per request:       68.658 [ms] (mean, across all concurrent requests)
Transfer rate:          645.71 [Kbytes/sec] received
Connection Times (ms)
min  mean[+/-sd] median   max
Connect:       53  114 409.7     55    3069
Processing:   332  569 243.9    474    2041
Waiting:       55   61  24.8     56     334
Total:        386  683 476.2    537    4472
Percentage of the requests served within a certain time (ms)
50%    537
66%    645
75%    745
80%    814
90%   1009
95%   1214
98%   2095
99%   3513
100%   4472 (longest request)