How using requestAction increased performance on my site

So originally when I built this site, I was in the “requestAction is bad” camp. So much so that I created a convoluted Component to assist in the creation of the sideboxes featured on this site. A sample of that code is as follows:

Show Plain Text
  1.  
  2. //Recent Posts
  3. $options = array(
  4.     'position' => 5,
  5.     'type' => 'list',
  6.     'format' => array(
  7.         'title' => '{n}.Node.title',
  8.         'id' => '{n}.Node.slug',
  9.         'url' => array(
  10.             'controller' => 'posts',
  11.             'action' => 'view'
  12.         )
  13.     ),
  14. );
  15. $recentPosts = $this->Post->recentPosts();
  16. $this->Sidebox->addBox('recentPosts', $recentPosts, $options);
  17.  

This snippet created the list of recent posts featured on many pages. There is a lot of different stuff munged in here, including data formatting and templating information. But at least I was able to avoid using requestAction, which was evil, or so I thought. After a while this bloat started to bother me. It wasn’t as flexible as I wanted and created lots of code that reminded me of urban sprawl. After doing some testing and realizing that requestAction was not the tortoise I once thought, I figured a refactor was in order. Before I started refactoring I did some benchmarking with siege.

Show Plain Text
  1. siege -b http://markstory.localhost/
  2.  
  3. Transactions:               1100 hits
  4. Availability:             100.00 %
  5. Elapsed time:             331.55 secs
  6. DATA transferred:           5.75 MB
  7. Response time:              3.00 secs
  8. Transaction rate:           3.32 trans/sec
  9. Throughput:             0.02 MB/sec
  10. Concurrency:                9.95
  11. Successful transactions:        1100
  12. Failed transactions:               0
  13. Longest transaction:            4.67
  14. Shortest transaction:           0.71

I ran all the tests on my home computer, so I don’t think they are indicative of real server performance. However, the performance delta should be similar.

I then started to refactor all these sideboxes into cached requestAction methods. This did two things, first it reduced the amount of controller code, and shifted a fairly hefty component into a much smaller helper. Since these sidebox elements only ever update when new content/comments are added I decided that caching them would create the largest performance improvement. After switching everything over to cached elements using requestAction. The end result was that in my views I would add the following:

Show Plain Text
  1. $sidebox->add('Recent Posts', 'sideboxes/recent_posts');

The above generated the same result as the code sprawl mentioned above. The SideboxComponent became a helper, that handles the collection and rendering of the sideboxes to the layout. Overall, I found it to be a much more elegant and efficient solution. Once I was happy with the code, I ran the same benchmarks and produced the following:

Show Plain Text
  1. siege -b http://markstory.localhost/
  2.  
  3. Transactions:               1100 hits
  4. Availability:             100.00 %
  5. Elapsed time:             209.36 secs
  6. DATA transferred:           5.64 MB
  7. Response time:              1.90 secs
  8. Transaction rate:           5.25 trans/sec
  9. Throughput:             0.03 MB/sec
  10. Concurrency:                9.96
  11. Successful transactions:        1100
  12. Failed transactions:               0
  13. Longest transaction:            2.82
  14. Shortest transaction:           0.36

So as you can see there are some huge improvements here. First off the requests/second has gone form 3.32 to 5.25 which is a 158% increase. Similar increases can be found in the response time, which dropped from 3 seconds to 1.9 seconds, again an approximate 158% increase. In the end using requestAction not only made reduced the amount of code I have to maintain, it also created huge performance improvements, now thats what I call a double win.

Comments

who knew

adam on 2/8/09

I love requestAction – In the admin areas of a site it’s sometimes useful to have the hasMany records visible on the edit screen of the main model. E.g. Post hasMany Comments – when you edit Post, you get the index view of comments under it (actually I have JS tabs that separate the post edit form from the comments index).

I use requestAction to get the “baked” rendered index view of comments for that post, which are paginated, then use Ajax pagination to page through the result set, all the while staying on the edit Post screen.

I use the approach of requestAction to initially load a bare view, then update the view with Ajax in quite a few other places as well – mainly just in the admin sections where performance is not critical, but hey, now I’ve seen this, using it in the main site may not be such a bad idea either.

Good post Mark, nice one, thanks for dispelling the myths.

Neil Crookes on 3/8/09

Nice article.

I am relativly new to cake. So I have a question. Why in the first place did you think “requestAction was evil”?

Akif on 3/8/09

@Akif: In /cake/libs/object.php you find the requestAction-function. It creates a new instance of the dispatcher which then processes the supplied argument (e.g. ‘/posts/show/15’). Your app has to create new instances for all needed classes (including models, helper etc.) and render the view which then gets passed back to your original call to requestAction().

The additional request blocks the first one.

This makes it slow and inefficient – in some cases, as you can see :)

leo on 3/8/09

Actually, this makes me wonder what’s going on behind the scenes with
$this->Sidebox->addBox(‘recentPosts’, $recentPosts, $options);
if a refactoring to requestAction manages to yield a 160% increase …

Could it be that using requestAction inderectly allows you to render a cached view ?

Bert Van den Brande on 6/8/09

Bert Van den Brande: I’m pretty certain that its the caching that is providing much of the speed increase. requestAction() makes that caching far simpler. Model result caching could be implemented inside the model methods instead of using cached requestActions. I felt it was simpler and more efficient (for a small site) to use requestAction instead of cached model methods.

mark story on 6/8/09

Agreed, and thanks for the feedback :)

Bert Van den Brande on 7/8/09

I agree completely that the caching is what’s yielding the performance improvement here.

I’ve also gotten tired of the “going around your elbow to get to your…” (well you know), approach required for dynamic elements on a page like your sidebar pieces.

What I found though, was that the code itself was really straightforward it was the over-the-top adherence to the architecture that made it a pain.

A simple:

$news = ClassRegistry::init(‘News’);

and suddenly the helper can grab the latest articles.

Adding Cache::read() and Cache::write() at the beginning and end respectively and we’re golden.

Goes well with the fat model structure too.

Taking a page from the FormHelper, I found that Helpers become extremely powerful for this sort of thing when you just use the class registry to access the model directly.

Barry on 25/9/09

Can you post the sidebox helper. It would be great for me as a begginer.

Rogério on 27/10/09

I avoid requestAction when I can, but use it sometimes (not as much as I cache).

I’m actually running into a requestAction problem, when from from a model (which is accessed directly from a shell) it’s not initializing helpers on the controller. // researching still, but if you’re familiar, let me know.

alan blount on 13/1/11

ignore my previous comment about helpers not initializing, stupid user error on my part.

as penance, here’s a helper method which does requestAction caching:


	/**
	* A simple helpful tool to get any request action as a cached element
	* @param string $actionPath
	* @param string $cacheEngine
	* @return string $requestActionResponse
	*/
	function cachedRequestAction($actionPath, $cacheEngine='default') {
		$cacheKey = 'helper_cached_request_action_'.md5(serialize($actionPath));
		if (($data = Cache::read($cacheKey, $cacheEngine)) === false) {
			$data = $this->requestAction($actionPath, array('return' => true));
			Cache::write($cacheKey, $data, $cacheEngine);
		}
		return $data; 
	}

alan blount on 13/1/11

is there any reason why requestAction doesn’t take into account $cacheAction ?

mich on 31/1/12

Have your say:

*
* You can use Textile markup, but be reasonable