Scaling and Optimizing your CodeIgniter Application, with Benchmarks
There’s been a few discussions recently about optimizing CodeIgniter applications to make them faster, more reliable, and scalable.
So, let’s look at some methods of doing this, with ‘real’ numbers and benchmarks.
First of all, we need to build a little controller that does ’stuff’.
It’s not extremely intensive, but it gives us a good base to test these optimization techniques.
function index() { $query = $this->db->get('module_pages'); $links = ''; if ( $query->num_rows > 0) { foreach ($query->result_array() as $page): $links .= '<a href="'.site_url($page['uri']).'">'; $links .= ucwords($page['title']).'</a><br />'; endforeach; $data['links'] = $links; } for ($i=0; $i < 10; $i++) { $this->db->like('title', 'London'); $query = $this->db->get('module_pages', 1); if ( $query->num_rows == 1 ) { $row = $query->row_array(); $row['body'] = str_replace('Getting', 'booya', $row['body']); $data['body'] = $row['body']; } } $this->load->view('welcome2', $data); }
And our view file is like this:
<?=$links?> <?=str_replace('booya', 'Getting', ucwords($body))?>
So, now let’s benchmark this little app… with the default CodeIgniter settings… no caching, active record debug on.
Concurrency Level: 10 Time taken for tests: 22.206642 seconds Complete requests: 1000 Failed requests: 0 Write errors: 0 Total transferred: 857000 bytes HTML transferred: 665000 bytes Requests per second: 45.03 [#/sec] (mean) Time per request: 222.066 [ms] (mean) Time per request: 22.207 [ms] (mean, across all concurrent requests) Transfer rate: 37.65 [Kbytes/sec] received
So, 45.03 Requests per second, not bad… but I think we can make some changes and see that number improve.
First off, let’s change:
foreach ($query->result_array() as $page):
to:
$pages = $query->result_array(); foreach ($pages as $page):
Let’s have a look at the performance difference:
Concurrency Level: 10 Time taken for tests: 21.391068 seconds Complete requests: 1000 Failed requests: 0 Write errors: 0 Total transferred: 857000 bytes HTML transferred: 665000 bytes Requests per second: 46.75 [#/sec] (mean) Time per request: 213.911 [ms] (mean) Time per request: 21.391 [ms] (mean, across all concurrent requests) Transfer rate: 39.08 [Kbytes/sec] received
So, as we can see… the improvement is very small, just 1.75 request/second quicker…
But they do all add up, so make sure you take a note of this optimization and use it from now on.
Now, onto Memcache, the ‘king’ of caching objects, arrays, you-name-it, in PHP
I start up the memcache service, then change the controller code accordingly:
$memcache = new Memcache; $memcache->connect('localhost', 11211) or die ("Could not connect"); $data = $memcache->get('view_data'); if ( !$data ) { $query = $this->db->get('module_pages'); $links = ''; if ( $query->num_rows > 0) { $pages = $query->result_array(); foreach ($pages as $page): $links .= '<a href="'.site_url($page['uri']).'">'; $links .= ucwords($page['title']).'</a><br />'; endforeach; $data['links'] = $links; } for ($i=0; $i < 10; $i++) { $this->db->like('title', 'London'); $query = $this->db->get('module_pages', 1); if ( $query->num_rows == 1 ) { $row = $query->row_array(); $row['body'] = str_replace('Getting', 'booya', $row['body']); $data['body'] = $row['body']; } } $memcache->set('view_data', $data, false, 3600) or die ("Failed to save data at the server"); } $this->load->view('welcome2', $data);
Now, let’s run the benchmark to see how this improves things.
Concurrency Level: 10 Time taken for tests: 17.124866 seconds Complete requests: 1000 Failed requests: 0 Write errors: 0 Total transferred: 857000 bytes HTML transferred: 665000 bytes Requests per second: 58.39 [#/sec] (mean) Time per request: 171.249 [ms] (mean) Time per request: 17.125 [ms] (mean, across all concurrent requests) Transfer rate: 48.82 [Kbytes/sec] received
Well, it’s faster, but not by much… I wonder how an opcode cache could improve things?
eAccelerator is an opcode cache extension for php… basically, the way that it works in ’stupid’ terms is like this:
eAccelerator interrupts just after PHP converts our code to its machine code and ‘caches’ it, so next time, it can skip the whole translation phase of execution.
Pretty simple huh?
Lets try the baseline code, with eaccelerator on the top of it.
Concurrency Level: 10 Time taken for tests: 10.122468 seconds Complete requests: 1000 Failed requests: 0 Write errors: 0 Total transferred: 857000 bytes HTML transferred: 665000 bytes Requests per second: 98.79 [#/sec] (mean) Time per request: 101.225 [ms] (mean) Time per request: 10.122 [ms] (mean, across all concurrent requests) Transfer rate: 82.59 [Kbytes/sec] received
Now, that’s more like it… here we’re still doing our SQL queries, and rendering all the data dynamically, but just caching the machine code, with a drastic improvement.
My main reason for doing this test was just to show off one of the features that I love most in CI, output caching.
Let’s see how well full-output caching works in CodeIgniter…
Some people believe that the overhead involved in writing the cache file outweighs the speed increase of actually rendering from a cache file.
$this->output->cache(3600);
Now we run our benchmark again, (baseline code, with the output caching on) and see if there’s a difference.
Concurrency Level: 10 Time taken for tests: 6.221538 seconds Complete requests: 1000 Failed requests: 0 Write errors: 0 Total transferred: 857000 bytes HTML transferred: 665000 bytes Requests per second: 160.73 [#/sec] (mean) Time per request: 62.215 [ms] (mean) Time per request: 6.222 [ms] (mean, across all concurrent requests) Transfer rate: 134.37 [Kbytes/sec] received
So, the difference huge, serving 160.73 requests per second, that’s a massive improvement… but I’m not going to stop there!
How about the ultimate? - Output caching, and opcode caching! - eAccelerator with CI Output caching:
Concurrency Level: 10 Time taken for tests: 2.565136 seconds Complete requests: 1000 Failed requests: 0 Write errors: 0 Total transferred: 857000 bytes HTML transferred: 665000 bytes Requests per second: 389.84 [#/sec] (mean) Time per request: 25.651 [ms] (mean) Time per request: 2.565 [ms] (mean, across all concurrent requests) Transfer rate: 325.91 [Kbytes/sec] received
Who’s the daddy then?
CodeIgniter!
For those of you struggling to digest those numbers… here’s a pretty little graph (made with codeigniter… yeah, I’m hardcore!)

As requested, here’s the results from a couple of other tests.
First off, serving the exact same output as static index.html:
1834.95 Requests per second… Amazingly fast.
And, replacing:
foreach ($query->result_array() as $page): $links .= '<a href="'.site_url($page['uri']).'">'; $links .= ucwords($page['title']).'</a><br />'; endforeach;
with:
foreach ($query->result() as $page): $links .= '<a href="'.site_url($page->uri).'">'; $links .= ucwords($page->title).'</a><br />'; endforeach;
Produces 46.19 [#/sec] (mean)…. again, a slight improvement over using arrays..
But hell, I love arrays!
Elliot
[...] Haughin, one of my CodeIgniter All-Star brethren, has posted an excellent article entitled Optimizing and Scaling your CodeIgniter Application. In this article, Elliot takes you from a baseline CodeIgniter installation, through memcache, [...]
First off, let’s change:
foreach ($query->result_array() as $page):
to:
$pages = $query->result_array();
foreach ($pages as $page):
—————————————————-
These two are almost the same thing.
Can you benchmark the example below for us?
while ($page = $query->_fetch_assoc()):
“active record debug on”
How?!
@John…
Active Record (DB) Debug is an option in config/database.php
$db['default']['db_debug'] = TRUE;
Just do one more thing for me, just for the fun of it:
Could you take the rendered HtML-code from that benchmarked page, save it as index.html, and run the benchmark again.
The reason: how fast is a ‘dumb non-php page’ on that same server. This way we can see the impact of just running php and CodeIgniter. I think the comparison would be interesting.
There we go guys… see the ‘further notes’ addon at the bottom of the post.
Elliot
And i should see the output in my log files? As far as i can see, i don’t get any extra info!
John,
db_debug - TRUE/FALSE (boolean) - Whether database errors should be displayed.
So SQL errors are outputted with this as true.
Elliot
[...] Haughin’s article on Optimizing and Scaling your CodeIgniter Application looks like a must read for anyone designing application and websites that wouldn’t qualify as [...]
I was one of those people who never bothered with caching, but you showed me the error of my ways today :p
I’m still struggling with caching in situations where the results must be real-time. Any tips?
Ah right, i see. Cheers!
Hey Jakob…
For an optimization that still produces ‘live data’, try using eaccelerator, that came out about twice as fast as baseline.
Elliot
What are you using to generate the benchmark information?
well done.
you wrote “For those of you struggling to digest those numbers… here’s a pretty little graph (made with codeigniter… yeah, I’m hardcore!)”
Hmmm, that would be nice to share
In your first screencast you talking about file routes.php and $installed_modules
IMHO shuld be $installed_controllers. Why, because name “module” is used for view modules. Some beginners may have problem what is routed controllers or view modules.
Nice work
piker
Hey guys, to answer a question….
Dan M: I’m using ‘apache benchmark’, (command: ab)
It’s a unix based benchmark tool that comes with apache. (httpd or httpd-devel packages).
why not modifying the cache logic & bypass php completely!
do not name the cached file cache/md5(something)
but cache/url
e.g. cache/catalog/page/5540
modify mod_rewrites .htaccess
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond cache/%{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L]
So you should have the ultimate cache at the second request of a ressource.
Greetings from
chris@belvini.de
Hi christoph,
That’s not a bad idea… but then you’d have to develop a cron task to delete the cache files after a certain age…
And, there may be some issues with headers that you’d need to sort out in your httpd.conf… but I’m definitely going to look into it.
Thanks,
Elliot
Great article, very useful and informative. Also thanks for turning me on to apache benchmark util, I was not aware of this.
Do you have any other recommendations on fine tuning our applications? Maybe an ongoing article series.
It would be useful to cache to completely static html files, as Christoph mentioned… but instead of a cron job, just provide an easy way to clear certain pages. Then, in your admin or form submission handlers you could simply specify which urls should be deleted (or directly regenerated then and there).
Result: fully static website where possible, with fully dynamic back-end. Best speed *and* most power
Elliot,
Awesome article!
I noticed you’ve got 10 concurrent requests taking place with “ab”. I tried the same (on my localhost), and noticed that I had 72 failed requests. Is there something I should be doing to allow for concurrent requests?
Thanks,
Frank
Hi Frank,
Thanks for the comment.
To answer your question….
A great deal of the ab configuration lies in your ’server’ spec.
These benchmarks were done on a brand new macbook pro, running apache2, mysql5, and php5. If your machine isn’t quite as high-performance, or your apache isn’t set up in a way to handle concurrent requests with ease, then you may experience failed requests.
You’ll need to finely tune your Apache’s StartServers, MaxServers, KeepAlives etc to handle more concurrency.
Elliot
Hey Elliot,
Thanks for getting back to me.
I’m running this test on a PowerBook G4, but I’m also running MAMP for my quick-dev setup.
I’ve checked the Apache conf and I see a few “StartServers” in different conditionals that check whether or not certain modules are loaded (ie: prefork.c, worker.c, etc.). Is there any specific module I need to make the change on? I’d love to get a better idea of the concurrency load my apps/sites are able to handle.
Thanks,
Frank
[...] Optimizing and Scaling your CodeIgniter Application - with Benchmarks! (tags: codeigniter optimization performance scalability php ci memcache eAccelerator caching) [...]