Stress-test your PHP App with ApacheBench
This article was sponsored by New Relic. Thank you for supporting the sponsors that make SitePoint possible!There’s no telling when your app might attract a throng of visitors at once – maybe it’s a Hacker News post that’s submitted at a specific second on a specific time of day (as posts there tend to work), maybe it was a particularly well placed Reddit post, and maybe it’s actually good and people noticed it, spreading it virally. Regardless of the reason, massive influxes of visitors are a double-edged sword: they get you what you always wanted – a chance to prove your worth to a large chunk of the internet’s population – but also often bring with them what you always feared: absolute downtime.
There are several ways to try and prevent this – among the most prevalent is deploying your application on a service like Amazon, Google App Engine or Heroku which have the ability to not only scale out and in rapidly at your command, but also support automatic scaling, if you’re not afraid of DDoS attacks also scaling your bills while you’re asleep. These platforms usually offer plugins that can optimize your application while it’s up, so you can fine tune it as you go along, but why not try and predict issues while still developing locally and save yourself time, money and effort in the long run?
Apache Benchmark
ApacheBench (also known as “ab”, the command you run it with) is a tool designed to nuke an endpoint with requests and load-test web servers. It supports a wide array of parameters and options you can tweak to simulate different loads, like number of requests, number of concurrent requests, extra headers, falsified cookies, and more.ab is often included with every Apache installation, but if not, you can easily install it by running
sudo apt-get install apache2-utils
. The current version of Laravel Homestead doesn’t have it, so if you’re following along please install it into that VM when you vagrant up
and connect through SSH.You can verify you have it installed by just running
ab
, which should produce a list of supported options:For our demonstration, I created a subfolder Laravel under Code in the VM, added a subfolder called “public”, and put an index.php file inside with the following content:
<?php
echo "Hello Test";
In homestead.yaml
, I called this site “homestead.app”, and in my hosts
file on my host machine I added 127.0.0.1 homestead.app
. This allows me to run sites hosted on the Homestead VM from the host machine’s browser via http://homestead.app:8000, all standard stuff we covered before. However, this also allows you to curl homestead.app
from inside the VM – something we can take advantage of when bombing our app with ab
.Test run
For starters, let’s try and nuke our
homestead.app
with a sample ab
grenade – just run ab
on the URL, without any options. Note that you must follow the URL with a slash, otherwise you’ll get an invalid URL error from ab.ab homestead.app/
We can see the benchmark finishes almost instantly – the number of
requests per second and concurrency load is too unreal and simple to
yield useful results. In fact, running the bench multiple times will
produce results from 50 to 200 possible requests per second. To provide
some more tangible results, we need to up our game.Demo app
Go outside the Laravel folder and remove it entirely withrm -rf Laravel
.
Next, we’ll create a default sample Laravel application with all
dependencies. If you don’t have Composer installed globally yet, do it, it’s easy and fast. Then, run the following command:composer create-project laravel/laravel Laravel --prefer-dist
This will download the Laravel skeleton, all dependencies, and
generate the autoload and lock files that are needed. You can verify it
works by going to http://homestead.app:8000 on your host machine, or curling homestead.app
inside the VM:Let’s tweak the basic bench command and properly step on our app’s toes. Run
ab -n 500 -c 100 homestead.app/
to make a more realistic test. The n
param means “number of requests”, while c
is concurrency, as in, how many requests at a time. Thus, this command will execute 5 batches of 100 simultaneous requests.The default result format you get at the end of the output will tell you how many requests (in percentage) were executed within a given timeframe, 10 by 10 per cent from 50% to 100%. In other words, if the first row is 50% 3200, that means half the total requests were executed within 3.2 seconds. For 250 requests, this isn’t bad, especially on such a weak machine.
Let’s try and slow things down on purpose. Go into
app/controllers/HomeController.php
and change the showWelcome
function to this: public function showWelcome()
{
if (isset($_GET['slower']) && $_GET['slower'] == 'true') {
sleep(1);
} else {
usleep(1);
}
return View::make('hello');
}
If you’d like to get some more consistent results, feel free to remove the call to Google Fonts from hello.php
,
as that removes the downloading lag when fetching the font and leaves
everything in the hands of the VM, locally. Also, in the routes.php
file, replace the current closure based route with this:Route::get('/', 'HomeController@showWelcome');
Now try benching two different URLs: homestead.app
and homestead.app?slower=true
.
As expected, you’ll notice the results differ wildly. This was a
completely ridiculous example, but it’s enough to demonstrate the effect
a long running script can have on your visitors when they flock in
en-masse. If you have long running scripts like these, it’s much better
to delegate them to the background via message queues or other means,
and make sure your client-facing script is as fast as you can make it.Conclusion
In this introduction to ApacheBench, we saw how much of an effect script efficiency had on huge traffic surges. We’ll be adding moreab
tutorials further down the line, but in the meanwhile, all you should
take away from this is – don’t underestimate small optimizations. There
is the potential of premature optimization, but if you can detect and
remove the boulders from the path sooner, the road will be smoother in
the long run. Play around with ab, test it out, switch out parameters
and options, add database connections to the sample Laravel app we
played around with – break it, fix it, break it again. Let us know what
you find – we’d love to publish your advanced use cases!Bruno Skvorc
Bruno
is a professional web developer from Croatia with Master's degrees in
Computer Science and English Language and Literature. After having left
his position as lead developer for a large online open access publisher,
he now works as the PHP editor for Sitepoint and on various freelance
projects. When picking them, he makes sure they all involve new and
exciting web technologies. In his free time, he writes tutorials on his blog and stalks Google's job vacancy boards.
https://plus.google.com/u/0/+BrunoSkvorc/posts
http://www.bitfalls.com/
0 comments: