I've been trying to improve the performance of my teeny-tiny virtual web server host running Drupal. It's been an interesting process, as serving a moderate-volume site within a 128MB virtual Linux host is, shall we say, constraining :)

Up until recently, one of the web sites I run has been served purely with static pages. That worked well for some years, but they've noticed a large drop-off in traffic over the years, as more community-based sites with a similar focus have cropped up, and their static HTML, no-forums, minimalistic approach wasn't serving very well.

They enlisted my help for a site make-over, with a focus on improving usability and giving them the ability to have members of their organization participate in submitting articles and comments to the web site. Since it's not entirely ready yet, I'm not at liberty to share the URL, but along the way I discovered some excellent tips to increase performance.

One of my main concerns is that I've run a web site that has been DOS'd before (http://barnson.org/). This runs Drupal, and although it held up reasonably well under the strain, even with the cache module enabled, page return performance wasn't as good as it should have been under load. I began looking for an ultimate-performance-for-minimum-memory option, and think I found it in the combination of Drupal, jpcache, and turck-mmcache.

Configuration of Turck-mmcache, which caches the compiled PHP code from a site, has already been covered in the Administration Guide for Drupal, so I won't touch on it here. But after much searching, I haven't found anyone else who's used jpcache in conjunction with Drupal.

To use jpcache requires a single hack in index.php. Part of my reason for posting this is to find out if anyone in the community sees a reason that this shouldn't work.

Unfortunately, I don't have a very good comparison numbers-wise vs. Drupal's built in SQL-based page cache mechanism. I'll have to try a more rigorous test here soon. For the time being, though, it seems that with very few modules, performance gains are minimal, since there are, apparently, still several database calls involved to create the $user object. Performance gains seem fairly substantial when many modules are loaded.

My goal is only to cache pages on the filesystem for anonymous users (like a DOS or a Slashdotting), not for any registered users. Here's the code. Only the section from //Begin through // End are the modified code; I included the include_once and drupal_page_header(); lines to give context for index.php.

include_once 'includes/bootstrap.inc';

// Begin jpcache check and page cache for anon users
global $user;
if (!$user->uid) {
  $cachetimeout=900;
  require "/var/www/drupal/jpcache/jpcache.php";
}
// End jpcache check and page cache for anon users

drupal_page_header();

As far as I can tell in parsing bootstrap.inc and the includes at the bottom of it, Drupal seems to make a database connection at database.inc (thus if mysql is down or oversaturated, you still can't get to cached pages, which partially defeats the purpose.), so using jpcache isn't yet as useful as it could be. However, it seems to speed things up a good deal.

Anyway, here are the raw numbers. I simply pulled down index.php, no options. For the first run, I had the jpcache hack in. For the second run, I removed the hack entirely. For the third run, I enabled Drupal's caching, logged out, and refreshed the index page to make sure it was cached before starting the run.

All runs of apachebench are "ab -n 100 -c 10 http://hostname/drupal/".

We have three configs: JPCACHE, NOCACHE, and DRUPALCACHE. The results are interesting, but not necessarily compelling.

JPCACHE configuration:

Time taken for tests:   5.494 seconds
Complete requests:      100
Failed requests:        0
Broken pipe errors:     0
Total transferred:      680000 bytes
HTML transferred:       638300 bytes
Requests per second:    18.20 [#/sec] (mean)
Time per request:       549.40 [ms] (mean)
Time per request:       54.94 [ms] (mean, across all concurrent requests)
Transfer rate:          123.77 [Kbytes/sec] received

Connnection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0     0    0.2      0     2
Processing:    45   508  288.8    464  2053
Waiting:       45   508  288.7    463  2052
Total:         45   509  288.6    466  2053

Percentage of the requests served within a certain time (ms)
  50%    466
  66%    567
  75%    647
  80%    687
  90%    896
  95%   1006
  98%   1213
  99%   2053
 100%   2053 (last request)

NOCACHE configuration:

Time taken for tests:   13.291 seconds
Complete requests:      100
Failed requests:        0
Broken pipe errors:     0
Total transferred:      675700 bytes
HTML transferred:       638300 bytes
Requests per second:    7.52 [#/sec] (mean)
Time per request:       1329.10 [ms] (mean)
Time per request:       132.91 [ms] (mean, across all concurrent requests)
Transfer rate:          50.84 [Kbytes/sec] received

Connnection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0     0    0.0      0     1
Processing:   291  1268  468.5   1247  2651
Waiting:      291  1267  468.5   1247  2650
Total:        291  1268  468.5   1247  2651
ERROR: The median and mean for the initial connection time are more than twice the standard
       deviation apart. These results are NOT reliable.

Percentage of the requests served within a certain time (ms)
  50%   1247
  66%   1410
  75%   1530
  80%   1597
  90%   1899
  95%   2085
  98%   2599
  99%   2651
 100%   2651 (last request)

DRUPALCACHE configuration:

Time taken for tests:   5.300 seconds
Complete requests:      100
Failed requests:        0
Broken pipe errors:     0
Total transferred:      684500 bytes
HTML transferred:       638300 bytes
Requests per second:    18.87 [#/sec] (mean)
Time per request:       530.00 [ms] (mean)
Time per request:       53.00 [ms] (mean, across all concurrent requests)
Transfer rate:          129.15 [Kbytes/sec] received

Connnection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0     0    0.0      0     1
Processing:    48   487  301.2    422  1407
Waiting:       47   486  301.1    422  1407
Total:         48   487  301.2    422  1407
ERROR: The median and mean for the initial connection time are more than twice the standard
       deviation apart. These results are NOT reliable.

Percentage of the requests served within a certain time (ms)
  50%    422
  66%    569
  75%    622
  80%    718
  90%    981
  95%   1127
  98%   1283
  99%   1407
 100%   1407 (last request)
DRUPALCACHE + JPCACHE (Drupal Cache enabled, jpcache hack enabled):
Time taken for tests:   4.861 seconds
Complete requests:      100
Failed requests:        0
Broken pipe errors:     0
Total transferred:      680000 bytes
HTML transferred:       638300 bytes
Requests per second:    20.57 [#/sec] (mean)
Time per request:       486.10 [ms] (mean)
Time per request:       48.61 [ms] (mean, across all concurrent requests)
Transfer rate:          139.89 [Kbytes/sec] received

Connnection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0     0    0.0      0     1
Processing:    58   458  270.0    385  1720
Waiting:       58   458  269.9    385  1719
Total:         58   458  270.0    385  1720
ERROR: The median and mean for the initial connection time are more than twice the standard
       deviation apart. These results are NOT reliable.

Percentage of the requests served within a certain time (ms)
  50%    385
  66%    533
  75%    632
  80%    653
  90%    760
  95%    906
  98%   1306
  99%   1720
 100%   1720 (last request)

Unfortunately, my test bed for these tests is a shared server, of a load that's totally unpredictable. However, looking at the 90th percentile results, it appears that jpcache and drupal's caching are nearly a statistical dead-heat independently. When used together, they appear to have some additional small benefits.

My hope for the future:

* I'd like to have a page-caching system that can continue to serve static pages even if MySQL is unresponsive
* I want to see if I can do a better check than "if (!$user->uid)" to determine if there is a user logged in to determine if we want to use jpcache or not. I think I should be able to do this with a session cookie check, instead; if the user has a valid session cookie for a registered user at all, don't jpcache. If their cookie is as an anonymous user, then let jpcache do its thing.
* I should probably revise my testing method. My apache processes, due to the memory constraints, only serve 20 pages before they kill themselves off, and I start with only 3 minspareservers, 5 maxspareservers. So it's very likely that these statistics indicate much less a performance difference in caching methods, but instead probably random variations in process spawning times. However, the difference between having no cache enabled, and having either jpcache or drupal's caching enabled, seemed fairly consistent with this test run a good representative sample.

I'll let you know my progress. It may turn out to be less of a problem if I were to just use a Squid front-end cache instead of jpcache or something, since memory is the real limitation on this tiny server.

Comments

mgifford’s picture

Surprised that you didn't get any response or comments on it.

Any progress on this since this first posting?

Mike
--
Mike Gifford, OpenConcept Consulting
Free Software for Social Change -> http://www.openconcept.ca

harry slaughter’s picture

I'm currently trying to figure out what options, if any, there are for taking down the db server and still being able to serve up (anonymous user style) content.

I used movabletype for many years. Initially, it was not a DB driven application. Pages were generated and stored when content was submitted. While this method is obviously no good for dynamic content, it's perfect for serving pages to the majority of users (who are anonymous).

It is occassionally necessary to take down a DB. It would be wonderful if Drupal had a way of limping along with filesystem cache rather than just serving up a single 'site down for maintainence' page (as in 4.7).

--
Drupal tips, tricks and services
http://devbee.com/ - Effective Drupal

--
Devbee - http://devbee.net/

mgifford’s picture

There have been a few discussions on this subject on the dev mailing list (I'm not activily following this at the moment, but have been alerted to them).

The problem has been largely that of having significant interest in the community I think. Especially with sites with lots of modules it is nice to be able to cache the content for anonymous users.

There are a number of ideas that have been floated about. Just need to get the critical mass of users together to develop, test & evaluate a solution for static caching.

Mike
--
Mike Gifford, OpenConcept Consulting
Free Software for Social Change -> http://openconcept.ca
http://learningpartnership.org http://open.bellanet.org/wufphotocomp/
http://ox.ca http://poped.org http://openoffice.ca

jmrukkers’s picture

Hi, I tried setting this up exactly as you described, but get garbled data sent back to my browser from the cache. I'm on Drupal 4.6.6. It looks like the output from drupal is already zipped, and when served up again does not get the appropriate headers added. I tried various settings in jpcache (v2 btw, zip on and off) but this does not resolve the issue. Did you make any changes to get this to work?
-- John

Yura Filimonov’s picture

Hello there.

Judging by this benchmark test, jpcache significantly outperforms Zend Optimizer and such. In my mind, this makes jpcache the choice for improving php performance.

What is the status of using jpcache and Drupal? Is it compatible? Any issues?