Benchmarking Zend Download Server
Recently I've started looking into ways that the PHP dev team in which I work can make better use of our Zend Platform installation.
For that reason, the recent Ibuildings/Zend seminar in London on the subject of "Enterprise PHP" was well timed, as it included a pretty detailed run through of a lot of what Platform has to offer.
One feature which really struck me as having the potential to bring performance benefits to one of our systems was the Zend Download Server. Back at the office, I looked into the feature, and ran a few benchmarks. Oddly though, the results don't seem to flatter Zend Download Server.
Zend Download Server
The premise behind Zend Download Server (ZDS) is that tying up valuable Apache HTTPD threads purely to serve static content is overkill, and far from efficient.
This is the same reason why lightweight webservers such as lighttpd are becoming popular. Lightweight webservers are typically run alongside a more powerful server such as Apache, and are dedicated to serving static content, leaving more Apache threads free to deal with the dynamic - for example PHP-based - requests.
ZDS follows that principle, although it works a little differently to lighttpd: it runs as a standalone process, but it hijacks a single Apache thread, thus allowing Apache to delegate the relevant requests down to ZDS.
ZDS can be utilised in a couple of ways. Firstly, there's 'transparent mode', whereby the administrator configures Platform in advance, telling it to hand specific downloads (say, all JPGs and GIFs over 128KB) off to ZDS.
The second option is 'manual mode', whereby the developer hooks directly into ZDS using a simple call to the proprietary zend_send_file() function. zend_send_file() is designed as a drop in replacement for functions such as fpassthru(), which simply read in the contents of a file and send them to output (in this case the HTTP response).
zend_download_file() seemed ideal for my needs, but not wishing to break the third rule of optimization, I decided to do a little benchmarking before I got too excited.
Benchmarking ZDS
I compared the two simplest possible scripts I could come up with. This first example uses the built-in PHP function, fpassthru():
<?php
$file = fopen('cat2.jpg', 'r');
fpassthru($file);
And here's the amended version of the script, using zend_send_file()to deliver the file:
<?php
zend_send_file('cat2.jpg');
Pretty straightforward stuff, all in all. Both scripts are delivering the same file, a JPG image of slightly less than 500KB.
I threw some load at the script using the ab benchmarking tool that ships with Apache HTTPD. Here's an example of the kind of command I ran:
./ab -n 200 -c 10 "http://platform/deliver_file.php"
The -n argument specifies the total number of requests, while -c specifies the number of concurrent requests that ab will try to make.
Here's an abridged version of the output using fpassthru():
Time taken for tests: 8.849252 seconds Requests per second: 22.60 [#/sec] (mean) Time per request: 442.463 [ms] (mean) Time per request: 44.246 [ms] (mean, across all concurrent requests) Transfer rate: 10818.09 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 2 87 514.0 14 4082 Processing: 96 337 182.3 281 1127 Waiting: 9 65 64.4 41 366 Total: 106 425 540.2 309 4364 Percentage of the requests served within a certain time (ms) 50% 309 66% 383 75% 463 80% 495 90% 613 95% 785 98% 3241 99% 4224 100% 4364 (longest request)
And the output using zend_send_file():
Time taken for tests: 8.886843 seconds Requests per second: 22.51 [#/sec] (mean) Time per request: 444.342 [ms] (mean) Time per request: 44.434 [ms] (mean, across all concurrent requests) Transfer rate: 10721.58 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 1 82 530.4 15 4420 Processing: 62 338 183.1 285 1069 Waiting: 12 41 20.1 38 148 Total: 63 420 595.1 304 5022 Percentage of the requests served within a certain time (ms) 50% 304 66% 370 75% 500 80% 545 90% 640 95% 741 98% 1096 99% 4992 100% 5022 (longest request)
I tried a few combinations of the various parameters available to ab, and quite honestly couldn't find any conclusive difference in performance between using ZDS and not using it. In fact, under low or very high load, zend_send_file() seemed to slow things down a little.
Conclusions
I'm pretty surprised - enough to doubt the validity of my tests, I admit. I don't believe for a minute that Zend would make false claims for features of their flagship product, so I must be doing something wrong. But what?
I'm aware that I'm measuring network speed as much as anything, and that the claimed benefits of ZDS centre around reduced load on the server side. But still, who cares if load is down when, at the end of the day, performance doesn't improve?
The one concrete lesson that I can offer up from all of this is that it's always valuable to follow the third rule, and Profile Before Optimizing.
Ciaran McNulty
I think the advantages of zend_send_file might come out when in a situation where PHP processes typically have a high memory usage.
On a project I've helped out with recently, most of the PHP scripts use about 20MB of memory per request. As the VPS it runs on has only 256MB RAM, we've had to set MaxClients to 8 in Apache.
Now, what we've found is that this affects even the few rare lightweight PHP scripts that are present, that just do things like serve up cached files, because the server has this hard limit of 8 simultaneous requests.
The promise of zend_send_file for me is that these lightweight processes get to 'opt out' of the MaxClients limit and instead hand off the file delivery to the one zend platform process, freeing Apache up to serve another request.
How to properly profile this, I've not yet worked out!