Sam de Freyssinet

In an earlier article written for techPortal, , the Hierarchical-Model-View-Controller architecture was explored. Using an example web application called Gazouillement and the Kohana Framework, the article investigated how structuring code using an HMVC methodology can help overcome some common scalability challenges in complex software architectures. The article concluded by demonstrating the relative simplicity of horizontally scaling the HMVC Gazouillement example application, after analysis of the execution bottlenecks.

The previous article was intended to be a reintroduction to HMVC for the web application era. HMVC is not a new concept: it was originally referenced in a Java World article over ten years ago and based on an idea that dates back forty years. Todays rise in notoriety of HMVC might be due to the popularity it is enjoying in modern frameworks. Or it could be that the similarity in size and scope of modern web applications to their desktop cousins has given developers reason to revisit the HMVC architecture. Given the present interest in HMVC, this is a great time to explore the subject further and answer a few of the questions arising from the previous article.

Hierarchical-MVC has been shown to make large web applications easier to scale out, but there is a price to pay— namely overall performance. This article will investigate ways of improving performance within HMVC web applications using asynchronous processing and some good old caching techniques. Predominantly this article will use examples written for the Kohana Framework; however all the concepts portrayed here could apply to any framework or web application.

What's wrong with Hierarchical-MVC?

So far there has been nothing but praise for Hierarchical-MVC, but like most things it is far from being a silver bullet. As with everything in life, nothing comes for free. In this case, a clean logical structure applied to code costs in overall performance.

Lets consider the following code that could be found in a controller to load a user from a resource. Example A uses a direct database data provider, whereas Example B uses the HMVC method to get the same resource.

// Example A - Load user model directly in traditional style
$user_a = new User('john@doe.name')
			->load($db);
 
// Example B - Load user model using HMVC style
$user_b = Request::factory('users/john@doe.name')
			->method('GET')
			->header('Accept', 'application/json')
			->execute($json_decoder);

$user_a and $user_b contain the same data representation. But with all things being equal, the data in $user_a will be loaded and available much faster than the $user_b model. $user_a is using an injected database resource to load its contents, whereas $user_b is creating a request to an internal MVC triad for its data. Database connections are not known for their speed, yet the HMVC methodology still loses out. What is happening here?

First, lets look at the traditional method used to load the user model in Example A. To get the requested data the User model uses a database. Lets look at the instructions within the User::load() method.

  1. Initialise database connection with correct credentials
  2. Use the database defined for this application
  3. Select everything about the user john@doe.name from the users table and return the results
  4. Parse the result into the User model

This should be familiar to the vast majority of PHP developers as a standard method for retrieving data from a database. Now lets examine the execution for the second user in Example B, the Request::execute() method.

  1. Parse the uri defined within the request object and match to predefined route
  2. Load the controller matched within the route
  3. Ensure the detected action exists in the controller and execute
  4. Initialise database connection with correct credentials
  5. Use the database defined for this application
  6. Select everything about the user john@doe.name from the users table and return the results
  7. Parse the result into a json response and return
  8. Decode the json response into a variable

$user_b has double the number of instructions required to complete the execution over $user_a. Additionally the execution of steps 4 through 7 within Request::execute() are almost identical to the entire execution stack of User::load(). This is because the MVC triad containing the user logic stores user data in a database. HMVC is only abstracting the interface, not replacing the underlying persistent storage technology.

Example A and B process instruction set juxtaposed with time, visually demonstrating that Example A is almost twice as fast as Example B
(Select image to enlarge)

The instruction set shown above outlines how a framework such as Kohana handles HMVC. Change the example above to another framework and it is likely that the instruction set will grow further[1]. Move the MVC triad containing the User logic to another server and the stack increases even further again[2]! We can see from this example that HMVC is not going to provide a fast architecture, even if it does scale easily. So Hierarchical-MVC is not going to perform as well as traditional MVC in all situations. However there is much that can be done to reduce the performance discrepancy.

Gimme the cache!

When tasked with optimising a web application, developers will turn to caching as a tried and tested remedy to performance related ailments. Using a cache removes the need to repeatedly process the same instruction set after the first process iteration, reducing the total number of execution steps for a given controller. There is still the same performance problem for the first iteration of code execution, but subsequent iterations can be served by a cached result if available. The example below applies caching to the earlier code examples A & B.

// Setup the cache
$cache = Cache::instance('memcache');
$cache_key_a = 'user_a~john@doe.name';
$cache_key_b = 'user_b~john@doe.name';
 
// Example A - Load user model directly in traditional style (with cache)
// If $user_a is not cached
if ( ! $user_a = $cache->get($cache_key_a))
{
	// load the record from the database
	$user_a = new User('john@doe.name')
				->load($db);
 
	// Cache $user_a for one hour
	$cache->set($cache_key_a, $user_a, 3600);
}
 
// Example B - Load user model using HMVC style (with cache)
// If $user_b is not cache
if ( ! $user_b = $cache->get($cache_key_b))
{
	// load the record from the MVC triad
	$user_b = Request::factory('users/john@doe.name')
				->method('GET')
				->header('Accept', 'application/json')
				->execute($json_decoder);
 
	// Cache $user_b for one hour
	$cache->set($cache_key_b, $user_b, 3600);
}

Example A and B process instruction set juxtaposed with time, including caching, demonstating both A and B are equal with valid cache available
(Select image to enlarge)

The example above balances the performance between the HMVC and traditional method of loading the User for all subsequent requests for either model. Memcache is a good choice in this case as it is distributed, allowing the entry to be read by other MVC triads. Other caching technologies could be used, however Memcache ensures all MVC triads within any domain can access the cached record. Localised cache technologies such as APC or Xcache can be used in Hierarchical-MVC architectures, however they should only be used where the cache is only required within the MVC triad being executed.

At this point the original problem of performance has been solved with working solution. But this solution is not very scalable as the caching information is explicitly defined within the controller receiving the data. If the application is scaled out and MVC triads moved to alternate domains, changes to the caching rules would require developers to alter code in disparate parts of the system, thus removing an important advantage of a HMVC architecture.

One of the fundamental features of Hierarchical-MVC is the exclusive use of HTTP interaction with MVC triads through their controller. If the caching rules could be transferred with the model data, the caching logic can be controlled by the MVC triad providing the data. This ensures the MVC triad responsible for the data is also responsible for the caching rules, rather than the controller or model receiving it as shown above. This sounds appealing, but how can this work in practice?

When retrieving data from a database, there is no easy way to append caching data to the result as metadata without encoding it into the result itself. What would be good is if HMVC could supply the caching information as metadata alongside the resulting record. Fortunately the interaction between MVC triads is using the HTTP protocol as an interface. This provides a long established method for supplying caching information within HTTP responses using the Cache-Control header. The HTTP/1.1 specification (also known as RFC 2616) provides a set of caching rules for the HTTP protocol. It is advisable that developers wishing to implement the HTTP protocol cache controls fully read and understand RFC 2616 prior to implementation as the rules are complex. PHP developers should use the HTTP PECL extension, which has implemented HTTP Cache-Control logic within HttpResponse::setCache().

All caching interpretation within HMVC should be contained within the client as it is with HTTP. The client in HMVC is the code that executes the request and parses the corresponding response. In the upcoming Kohana Framework version 3.1, the Kohana_Request_Client class executes requests and contains all of the caching logic. In other frameworks it should probably reside within the dispatcher or similar class depending on the implementation. Once the framework is ready to handle Cache-Control headers, it is a simple task to include the caching information within responses. Lets take a look at the Messages controller from Gazouillement, updated since the previous article.

class Controller_Messages extends Controller_REST {
 
	// Accept formats supported by this controller
	protected $_accept_formats = array(
		'application/json',
		'application/xhtml+xml',
		'appllication/atom+xml',
	);
 
	// Response format
	protected $_response_format;
 
	// The user context of this request
	protected $_user;
 
	// this code will be executed before the action.
	// we check for valid format and user
	public function before()
	{
		// Sort the Accept header values only
		$this->request->sort_values_by_quality(array('accept'));
		// Test to ensure the response format requested is acceptable
		if ( ! $accept = array_intersect($this->request->headers['accept'], $this->_accept_formats))
		{
			throw new Controller_Exception('Accept values unsupported by this resource, please use '.implode(', ', $this->_accept_formats), 406);
		}
 
		// Set the response format to first matched accept format
		$this->_response_format = current($accept);
 
		// Next test the username is valid
		$this->_user = new Model_User($this->request->param('user');
 
		if ( ! $this->_user->loaded())
		{
			throw new Controller_Exception('Unknown resource requested.', 404);
		}
 
		return parent::before();
	}
 
	// This finds the users messages
	public function action_get()
	{
		// Load messages using user 1:M messages relation
		$messages = $this->_user->messages;
 
		// Set the response, using a prepare method for correct output
		$this->response->body($this->_prepare_response($messages));
 
		// Add caching information to allow caching for five minutes
		$this->response->headers('cache-control', 'max-age=300');
 
		Add the correct content type to the header
		$this->response->headers('content-type', $this->_response_format);
	}
 
	// Method to prepare the output
	protected function _prepare_response(Model_Iterator $messages)
	{
		// Return messages formatted correctly to format
		switch ($this->_response_format)
		{
			case 'application/json' :
			{
				return json_encode($messages->as_array());
			}
			case 'application/xhtml+xml' :
			{
				return View::factory('messages/xhtml', $messages);
			}
			case 'application/atom+xml' :
			{
				return Atom::create_feed($messages)
						->render();
			}
			default :
			{
				throw new Controller_Exception('Unknown error has occurred, content type could not be parsed', 500);
			}
		}
	}
}

The messages controller makes use of a RESTful pattern and there are some caching instructions applied to the response. Lets step through this controller to examine the properties and methods in detail.

// Accept formats supported by this controller
protected $_accept_formats = array(
	'application/json',
	'application/xhtml+xml',
	'appllication/atom+xml',
);
 
// Response format
protected $_response_format;
 
// The user context of this request
protected $_user;

The controller has some refactored and new properties that define the behaviour of this controller. $_accept_formats provides the supported response formats, which will be validated against the Accept header of the request. The resolved response format is stored separately in $_response_format and the user context of the request is stored in the original $_user property.

public function before()
{
	// Sort the Accept header values only
	$this->request->sort_values_by_quality(array('accept'));
	// Test to ensure the response format requested is acceptable
	if ( ! $accept = array_intersect($this->request->headers['accept'], $this->_accept_formats))
	{
		throw new Controller_Exception('Accept values unsupported by this resource, please use '.implode(', ', $this->_accept_formats), 406);
	}
 
	// Set the response format to first matched accept format
	$this->_response_format = current($accept);
 
	// Next test the username is valid
	$this->_user = new Model_User($this->request->param('user');
 
	if ( ! $this->_user->loaded())
	{
		throw new Controller_Exception('Unknown resource requested.', 404);
	}
 
	return parent::before();
}

Before the controller action requested is executed by Kohana, the request client will execute the Kohana_Controller::before() method. Zend Framework developers can use the Zend_Controller::preDispatch() method to achieve the same logic. Most other web application frameworks have an equivalent method. This method sets up the controller before each action is executed, not to be confused with the standard constructor that will only be invoked upon instantiation. The before() method defined above ensures that the request Accept header matches the supported response formats and that the user parameter is valid for this context. If either of the criteria fail inspection, the correct response code is returned from the controller.

// This finds the users messages
public function action_get()
{
	// Load messages using user 1:M messages relation
	$messages = $this->_user->messages;
 
	// Set the response, using a prepare method for correct output
	$this->response->body($this->_prepare_response($messages));
 
	// Add caching information to allow caching for five minutes
	$this->response->headers('cache-control', 'max-age=300');
 
	Add the correct content type to the header
	$this->response->headers('content-type', $this->_response_format);
}

Once the Kohana_Controller::before() method has finished executing, the request action method will be executed. In this method the messages are loaded from the relevant model and processed before being applied to the response body. Then the Cache-Control header is set to five minutes and the content type is set to the appropriate value. The Kohana_Controller::$response object is rendered appropriately by the client, so there is no need to render it within controller actions.

// Method to prepare the output
protected function _prepare_response(Model_Iterator $messages)
{
	// Return messages formatted correctly to format
	switch ($this->_response_format)
	{
		case 'application/json' :
		{
			return json_encode($messages->as_array());
		}
		case 'application/xhtml+xml' :
		{
			return View::factory('messages/xhtml', $messages);
		}
		case 'application/atom+xml' :
		{
			return Atom::create_feed($messages)
					->render();
		}
		default :
		{
			throw new Controller_Exception('Unknown error has occurred, content type could not be parsed', 500);
		}
	}
}

Finally, the Controller_Messages::_prepare_response() method formats the response correctly according to the resolved response format. This has no effect on caching of the response ultimately, but it does demonstrate a method for supporting multiple response formats within controllers.

Now the controller is defined it is ready to receive requests. For the most part the HTTP interaction is hidden from developers, abstracted by the framework or server language the application is implemented using. It is important to understand the raw interaction, so for clarity the HTTP request and resulting response are shown below. Users of Kohana can access the true HTTP representation within the upcoming version 3.1 by invoking the render() method on either the request or response object.

Request


GET /samsoir/messages HTTP/1.1
Accept: application/json

Response


HTTP/1.1 200 OK
Cache-Control: max-age=300
Content-Type: application/json
Content-Length: 90

{messages: [{msg: 'This is a test message', date: '2010-10-10 14:51:34 GMT+1'}], total: 1}

The resulting response contains the messages for the user samsoir in json format. Included within the response header is the Cache-Control directive providing an instruction to cache the resource for five minutes. The controller responsible for messages has now taken control of the cache settings, ensuring all cache logic is maintained within the MVC triad responsible for the data. All clients interpreting this response will cache the resource for five minutes unless the cache is invalidated.

Caching should be applied to web applications in layers. Using the Cache-Control header for caching instructions has an added benefit of allowing any HTTP interface to store the cache correctly. Reverse proxy servers such as Varnish and Squid can also provide an additional layer of cache if placed between the client and controller. By adding a proxy server that can cache to the architecture, it is straightforward to add an additional caching layer to Gazouillement without changing any controller logic.

How do other controllers and models get messages from this controller now it has caching headers applied? If the client executing the request understands HTTP cache control headers, then no additional code will be required. For existing clients that cannot parse HTTP Cache-Control headers natively, it is highly recommended to use the PECL HTTP extension. Below is an example using Kohana Framework 3.1, which does understand Cache-Control directives.

class Controller_Index extends Controller {
 
	// Example controller pulling messages for 'samsoir' from the messages MVC triad
	public function action_index()
	{
		// Load messages using Request class that understands cache-control
		$messages = Request::factory(Route::url('messages', array('user' => 'samsoir')))
			->header('accept', 'application/json')
			->execute()
			->response;
	}
}

Messages controller example to demonstrate how caching increases performance of the Gazouillement messages controller
(Select image to enlarge)

Because the Kohana_Request class understands how to handle the HTTP Cache-Control header, the request client will only execute a new request for resources if the cached response has become stale or invalidated. The code responsible for loading the messages resource does not have to change or add any additional cache control logic. All of the caching information is provided in a format that can be interpreted by numerous consumers, without augmenting the standard structure of the data returned. We have now implemented a scalable way of caching MVC triad responses that respects the Hierarchical-MVC conventions, ensuring all caching logic for each triad is maintained within it own domain.

Parallel Processing

So far we have optimised the execution time of single requests to Hierarchical-MVC resources to ensure that they can perform as quickly as more traditional methods for loading data. For many this may be enough to get their applications performing to expected metrics. But all requests are still happening synchronously. A better solution would be to load HMVC resources asynchronously in one operation. Unfortunately PHP does not lend itself to symmetric multiprocessing, restricting PHP code to executing in sequence on a single thread even when executed on multi-core systems.

The architecture of Hierarchical-MVC enables applications to run across multiple systems and software languages because of the HTTP interfaces used between the triads. Because resources are requested rather than directly processed, there is scope to run requests in parallel even in a language that does not support symmetric multiprocessing such as PHP.

Once again lets look at the Gazouillement application, this time at the index controller used for the users' homepage. This controller pulls a number of resources from other parts of the system before presenting them to the client.

// Handles a request to http://gazouillement.com/samsoir/
class Controller_Index extends Controller {
 
	public function action_index()
	{
		try
		{
			// User URI generated using reverse routing
			$user_uri = Route::url('users', array
				(
					'user'  => $this->request->param('user')
				));
 
			// Load the user
			$user = Request::factory($user_uri)
					->method('GET')
					->header('accept', 'application/json')
					->execute($json_decoder);
		}
		catch (Request_Exception $e)
		{
			// If the exception is a 404 error
			if ($e->getCode() == 404)
			{
				// Let the controller handle that exception
				throw new Controller_Exception_404('Unable to load user :user', array(':user' => $this->request->param('user')));
			}
			else
			{
				// Else re throw the exception
				throw $e;
			}
 
		}
 
 
		// Messages URI generated using reverse routing
		$messages_uri = Route::url('messages', array
			(
				'action' => 'find',
				'user'   => $user->name
			));
 
		// Load messages for user in xhtml format
		$messages = Request::factory($messages_uri)
			->header('accept', 'application/xhtml+xml'
			->execute()
			->response;
 
		// Relations URI generated using reverse routing
		$relations_uri = Route::url('relations', array
			(
				'action' => 'following',
				'user'   => $user->name
			));
 
		// Load relationships for user in xhtml format
		$relations = Request::factory($relations_uri)
			->header('accept', 'application/xhtml+xml')
			->execute()
			->response;
 
		// Apply the user index page view to the response
		// and set the user, messages and relations to the view
		$this->request->response = View::factory('user/home', array(
			'user'      => $user,
			'messages'  => $messages,
			'relations' => $relations
		));
	}
}

Controller_Index represents a typical Hierarchical-MVC controller that needs to load many different resources from across the system. It uses a RESTful interface when loading resources, and reverse routing to reduce overhead when MVC triads move location. The problem with this controller is that it is very linear. Each instruction must complete before the next is executed, and this means that the total processing time is at the mercy of the connections to the other resources.

Diagram demonstrating the long execution time of the Controller_Index::action_index() method using synchronous processing
(Select image to enlarge)

It would be better if the loading of those external resources could be processed asynchronously rather than in sequence. To do asynchronous processing of resources, it is important to examine exactly what can be processed and when. Bundling all three of the resource calls up into one asynchronous process within the controller action would cause a logical failure. The request for relations and messages have a strong dependency on the user being available, therefore the user has to be loaded before the messages and relations can be loaded. But once the user is available, the other related resources can be loaded asynchronously as neither have any strong dependencies on other resources within the action.

PHP provides two standard models for creating asynchronous HTTP requests, curl_multi_exec and HttpRequestPool— the latter is part of the PECL HTTP extension. It should be noted that the HttpRequest class uses Curl internally, but does provide a nice Object-Orientated interface to HTTP operations.

Using the HttpRequestPool it is possible to optimise the execution of the Controller_Index class defined before.

public function action_index()
{
	try
	{
		// User URI generated using reverse routing
		$user_uri = Route::url('users', array
			(
				'user'  => $this->request->param('user')
			));
 
		// Load the user
		$user = Request::factory($user_uri)
				->method('GET')
				->header('accept', 'application/json')
				->execute($json_decoder);
	}
	catch (Request_Exception $e)
	{
		// If the exception is a 404 error
		if ($e->getCode() == 404)
		{
			// Let the controller handle that exception
			throw new Controller_Exception_404('Unable to load user :user', array(':user' => $this->request->param('user')));
		}
		else
		{
			// Else re throw the exception
			throw $e;
		}
 
	}
 
 
	// Messages URI generated using reverse routing
	$messages_uri = Route::url('messages', array
		(
			'action' => 'find',
			'user'   => $user->name
		));
 
	// Relations URI generated using reverse routing
	$relations_uri = Route::url('relations', array
		(
			'action' => 'following',
			'user'   => $user->name
		));
 
	// START REFACTORED CODE
 
	// Load messages for user in xhtml format
	$messages = new HttpRequest($messages_uri, HTTP_METH_GET, array
		(
			'headers'  => array
			(
				'accept' => 'application/xhtml+xml'
			)
		));
 
	// Load relationships for user in xhtml format
	$relations = new HttpRequest($relations_uri, HTTP_METH_GET, array
		(
			'headers'  => array
			(
				'accept' => 'application/xhtml+xml'
			)
		));
 
 
	// Create an asynchronous request pool
	$pool = new HttpRequestPool($messages, $relations);
 
	try
	{
		// Execute the requests
		$pool->send();
 
		// Wait for all requests to finish
		while ($pool->getAttachedRequests() != $pool->getFinishedRequests())
		{
			// Wait a moment (allows cpu time to be freed for other processes)
			usleep(1);
		}
	}
	catch (HttpRequestException $e)
	{
		// Something went wrong with a request, handle gracefully
	}
 
	// END REFACTORED CODE
 
	// Apply the user index page view to the response
	// and set the user, messages and relations to the view
	$this->request->response = View::factory('user/home', array(
		'user'      => $user,
		'messages'  => $messages->getResponseBody(),
		'relations' => $relations->getResponseBody()
	));
}

Diagram demonstrating the shorter execution time of the Controller_Index::action_index() method using asynchronous processing
(Select image to enlarge)

The Controller_Index action is now executing the requests for messages and relations in parallel, resulting in improved performance for this controller and a better experience for users. It is important to remember to resolve dependencies first when implementing asynchronous requests. There is no guarantee of which request will complete first, so asynchronous requests must have all resources required available before execution. As the User model is required by the messages and resources requests, it must be loaded ahead of the asynchronous process.

We have successfully introduced parallel processing into the execution stack of the Gazouillement application and improved the overall performance by caching the HMVC responses using the caching headers specified in HTTP/1.1. However there is a compromise to be addressed when this method of parallel processing used. When using HttpRequestPool we are creating a full HTTP request for a resource (see footnote[2]). This is not an issue when requesting resources that are located elsewhere, however it creates additional overhead if requesting from a local MVC triad. Therefore what is needed is a way of creating parallel requests within the same MVC triad— enter the Request Worker Pool. A future article (coming soon!) will discuss High Performance HMVC with Request Worker Pools, Clustering & the Cloud, addressing this last problem and providing the answer to localised parallel processing with HMVC.

In this article we have revisited the Hierarchical-MVC architecture with the aim of improving the overall performance of the entire process stack within Gazouillement. The first optimisation vector was directed at applying caching using HTTP Cache-Control headers, ensuring all caching logic was encapsulated within the MVC triad responsible for the data. After this optimisation Gazouillement only loaded resources that were absolutely necessary, reducing the total number of executions required to complete a request. The next optimisation was to implement asynchronous requesting where appropriate, allowing the Gazouillement application to parallel process within controller actions. Combined these two optimisation techniques ensure that Hierarchical-MVC architectures can scale horizontally whilst maintaining overall performance.

  1. The view action helper with Zend Framework 1.x re-bootstraps the application when creating a request to another controller action.
  2. In addition to the steps outlined, creating a request to another server adds the overhead of opening a HTTP connection to the server, processing the request and bootstrapping the application.
Digg This
Reddit This
Stumble Now!
Buzz This
Vote on DZone
Share on Facebook
Bookmark this on Delicious
Kick It on DotNetKicks.com
Shout it
Share on LinkedIn
Bookmark this on Technorati
Post on Twitter
Google Plus

17 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

  1. Thanks samsoir for another nice and handy article on HMVC. As a seasoned kohana developer most of the information was pretty much obvious, but asynchronous requests was a new technique to me. Also this article and the previous one are the mines of information for the new-comers of kohana.

    Thanks again

  2. Good article, again! Wouldn't this only providing caching to a same user and his browser, there where Memcache it's cache is available for everyone?

  3. Nice! I'm glad there is someone pioneering HMVC for web apps like this. I would have never even considered the parallel subrequests a possibility.

  4. Aaron Zampaglione said

    Excellent article; very easy read and a great example! Hopefully you don't mind answering a few questions...

    - Any particular reason you didn't use the Response::create_cache_control() (simplicity of the example I suppose)?

    - If "/samsoir/messages" updated very frequently and had a very large response body, would it be better to use "Cache-Control: no-cache, must-revalidate"? My impression was that "no-cache" validates with the server for changes before giving a cached copy; which would be advantageous if you required accurate data without losing all the benefits of caching?

    - Assuming you don't want the requests to poll the server for validation, is there anyway to "force" re-validation cleanly if an update does occur within those five minutes? Is there a recommend alternative to caching frequently updated, large responses? If so, how would this be implemented in Kohana?

    Thanks for your time!

  5. Very insightful article. I've just been using Kohana and I'd like to develop my websites the right way from the start, so this came in handy.

  6. Rick said

    Excellent work Sam.

    I like how you're using Kohana routes for both internal and external http requests. External requests are still using curl as opposed to the Kohana Request class, so I'm looking forward to your next article where hopefully internal and exteranl http requests will share one seamless implemention.

  7. All the parsing of http response headers and serialization+immediate deserialization to json of the model object still seems a huge waste of time to me. And adding cache is not a panacea, as cache expiry is one of the worst hairy balls in the field of IT.
    But I agree that factoring out subrequests to other servers can be great for scalability.
    So why not improve the routing layer to have MVC triads that can be accessed either via http or direct php in a transparent manner based upon some config?

  8. Sam de Freyssinet said

    Firstly, apologies for the late responses from me. I have been away for the past week (more on this to come). I will handle the questions/points in order.

    @aaron:

    > Any particular reason you didn’t use the Response::create_cache_control() (simplicity of the example I suppose)?

    Entirely. Naturally I would recommend using a frameworks standard methods for dealing with HTTP headers and cache. However, to keep it simple and ensure nothing was encapsulated, I chose to create examples that were explicit in their implementation.

    > If “/samsoir/messages” updated very frequently and had a very large response body, would it be better to use “Cache-Control: no-cache, must-revalidate”?

    Caching is as much an art as a science. Each caching directive must be evaluated independently depending on the use case. Ultimately you cannot have all sides of the triangle, i.e. speed, data integrity, and resources- one must always be sacrificed.

    > Assuming you don’t want the requests to poll the server for validation, is there anyway to “force” re-validation cleanly if an update does occur within those five minutes? Is there a recommend alternative to caching frequently updated, large responses? If so, how would this be implemented in Kohana?

    Several questions there. Firstly, there is no standard HTTP method for forcing re-evaluation of an existing rule interpreted by the client with Cache-Control. Naturally, Etags avoid this problem, but with additional resource required. There is scope to bypass this issue, but I wouldn't recommend them and they go beyond the scope of this article. As I alluded to earlier, there is always a tradeoff with caching. High availability vs integrity is an expensive proposition- each situation must choose which they will favour most. Finally, Kohana does not provide a solution to this design issue.

    @gggeek

    > All the parsing of http response headers and serialization+immediate deserialization to json of the model object still seems a huge waste of time to me. And adding cache is not a panacea, as cache expiry is one of the worst hairy balls in the field of IT.

    I largely expected at least one response exclaiming this, and for the most part I complete agree with your sentiment. PHP does not lend itself to an HMVC architecture. These articles are just a few ideas of how to overcome the issues presented by this particular system design. However, I don't really agree with your comments about cache expiry. Any failure of caching rules is purely due to the client I believe, unless you wish to elaborate further.

    > So why not improve the routing layer to have MVC triads that can be accessed either via http or direct php in a transparent manner based upon some config?

    Precisely. Hopefully my next (and final) article on the subject will clear this up. I did allude to this at the end of the article by the way ;)

  9. An excellent article, very interesting to read. However, one I can't understand: controller actions in PHP MVC frameworks are basically all object methods. If you are calling one in the same application (it's fully OK if the action resides on another domain or server), why make a trip through an http connection as a "real" client would do, why not call the method directly? You could otherwise make the request asynchronously for sure (what is not really possible with normal method calls in PHP), but I think the overhead caused by the unnecessary round trip is also significant.

    I use the following process in my own framework:
    1) Incoming http request (user opening a page) will be parsed by the front controller and the control dispatched to a controller and action.
    2) Controller instance created and action called.
    3) If the action uses the hierarchical MVC capabilities, then it can make some "inner requests": these are similar to "outer" ones, but have the following properties.
    4) The front controller gets an inner request (the code in the action calls a front controller method with an url), parses (but more lightly than an outer request, e.g. the url won't be rerouted) it, instantiates the controller and calls the action. The action can return a value as every PHP function, so the calling action can get data back.
    This step only needs an "inner request" to make the instantiation of the controller and the calling of the action easier. One could also include the controller file, instantiate it by hand and call the action manually. Also, the inner request (as the "outer request") works from an "url". This is an url that could also called from the outer world to run an action (it's something like this: [controller]/[action]/[param1]/[param2]...), however public urls are all "nice" ones, therefore they are rerouted inside the front controller. This is not needed with inner requests, as the programmer can deal with ugly urls...

    Example:

    class ExampleController
    {
    public function exampleAction()
    {
    $exampleWidget = FrontController::innerRequest("widgetcontroller/nicebutton"); // could be anything, not only widgets
    echo "I come from another controller! " . $exampleWidget;
    }
    }

  10. Wonderful article and beautifully illustrated with the diagrams. I have been looking at various articles on HMVC for some time now after completing a number of large projects in CodeIgniter 1.7.x and feeling its limitations. After comparing all the frameworks out there, this article has finally given me the confidence I need to switch to Kohana for the next big project!

    Very good job, thanks for your efforts!

  11. Sam de Freyssinet said

    @Piedone

    In most MVC frameworks, what you describe is actually what happens, Zend, Kohana, CI, Symfony all follow that pattern. Kohana does some nice abstraction allowing a unified interface for internal and external connections, but ultimately internal calls do not require HTTP connections. This was the subject of my previous article, linked at the top of this page. Naturally it is inefficient to use HTTP connections for local endpoints and this is certainly not what this article is suggesting.

    @Adam

    Thank you for your comments.

  12. Paul said

    Thanks a lot for article!
    Which tool is used to draw these nice execution diagram?

  13. Sam de Freyssinet said

    @Paul I use Omnigraffle for all my graphics in this and the previous article.

Continuing the Discussion

  1. Ibuildings techPortal: Optimising MHVC Web Applications for Performance | Development Blog With Code Updates : Developercast.com linked to this post on November 17, 2010

    [...] the Ibuildings techPortal there’s a new tutorial from Sam de Freyssinet that follows up on a previous article he wrote about using HMVC [...]

  2. abcphp.com linked to this post on November 18, 2010

    Optimising HMVC Web Applications for Performance...

    A technical tutorial packed full of great scalability and performance tips to make your HMVC-driven application the best it can be. A series of strategies are put forward, with illustrations of how this alters the operation and what improvements can be...

  3. Professional Joomla Web Design & Development Services from Perth - Wordpress 201 linked to this post on November 28, 2010

    [...] Optimising HMVC Web Applications f&#959r Performance – techPortal [...]

  4. Why Kohana is an awesome framework | Shameer's Blog linked to this post on January 26, 2011

    [...] Please check this link to read more about scaling application using kohana. Of course this scalability comes with a performance cost. But we can significantly improve the performance through caching and parallel processing. [...]

Some HTML is OK

(required)

(required, but never shared)

or, reply to this post via trackback.