Federico Cargnelutti

Simple is better than complex. Complex is better than complicated. | @fedecarg

Improving the performance of Zend_Controller

with 27 comments

Zend_Controller_Router_Rewrite is the standard framework router. It takes a URI endpoint and decomposes it into parameters to determine which module, controller, and action of that controller should receive the request. Routing occurs only once, when the request is initially received and before the first controller is dispatched.

To use the router you have to instantiate it, add some user defined routes and inject it into the controller:

$routes['blog'] = new Zend_Controller_Router_Route(
	'blog/:year/:month/:day/:title/*',
	array(
		'year'       => date('Y'),
		'month'      => date('m'),
		'day'        => date('d'),
		'module'     => 'blog',
		'controller' => 'index',
		'action'     => 'archive'
	),
	array(
		'year'  => '\d+',
		'month' => '\d+',
		'day'   => '\d+',
		'title' => '([0-9a-zA-Z-]+)',
	)
);

$frontController = Zend_Controller_Front::getInstance();
$router = $frontController->getRouter();
$router->addRoutes($routes);
$frontController->dispatch();

Front Controller dispatch mechanism:

o-- Zend_Controller_Front::dispatch()
--> Zend_Controller_Front::setResponse()
--> Zend_Controller_Front::getRouter()
--> Zend_Controller_Router_Rewrite::setParams()
--> Zend_Controller_Router_Rewrite::route()

Zend_Controller_Front gets an instance of Zend_Controller_Router_Rewrite, routes the request, iterates through all the provided routes and matches its definitions to the current request URI.

The problem

The router has to load all the objects into memory, iterate through all the routes and perform regular expressions until it finds a positive match. This means that the more routes you add, the longer the router will take to process them. The problem becomes more critical when using XML or INI configuration files and a site increases in complexity and attracts more traffic.

The Solution

One of the solutions is to split the routes file into different .php or .ini files. For example, instead of loading a single file:

$frontController = Zend_Controller_Front::getInstance();
$frontController->addModuleDirectory(
	'../application/modules'
);
$config = new Zend_Config_Ini(
	'../application/config/routes/config.ini',
	'production'
);
$router = $frontController->getRouter();
$router->addConfig($config, 'routes');

You can use the My_Controller_Router_Rewrite class to set the path to the directory where all the configuration files are located:

$frontController = Zend_Controller_Front::getInstance();
$frontController->addModuleDirectory(
	'../application/modules'
);
$router = new My_Controller_Router_Rewrite();
$frontController->setRouter($router);
$router->setRoutesDirectory(
	'../application/config/routes',
	array('production', 'routes')
);

This allows the router to map the first segment of the URL path to a filename. For example, given the following URI:

http://www.zend-test.com/blog/2008/07/14/test

The router will load the blog.ini file:

application
|-- bootstrap.php
|-- config
|   `-- routes
|       |-- blog.ini
|       |-- eshop.ini
|       |-- forum.ini
|       `-- gallery.ini
|-- layouts
|   `-- main.phtml
`-- modules
    |-- blog
        |-- controllers
        |   `-- IndexController.php
        |-- models
        `-- views

Test Result Data

Zend_Controller_Router_Rewrite (4 routes)

Server Hostname:         www.zend-test.com
Document Path:           /blog/2008/07/14/test
Requests per second 1:   88.24 [#/sec]
Requests per second 2:   89.32 [#/sec]

Zend_Controller_Router_Rewrite (16 routes)

Server Hostname:         www.zend-test.com
Document Path:           /blog/2008/07/14/test
Requests per second 1:   64.45 [#/sec]
Requests per second 2:   64.61 [#/sec]

Zend_Controller_Router_Rewrite (20 routes)

Server Hostname:         www.zend-test.com
Document Path:           /blog/2008/07/14/test
Requests per second 1:   59.17 [#/sec]
Requests per second 2:   59.03 [#/sec]

Now, lets try splitting the config.ini file into separate configuration files:

MyRouter extends Zend_Controller_Router_Rewrite (4 routes)

Server Hostname:         www.zend-test.com
Document Path:           /blog/2008/07/14/test
Requests per second 1:   89.47 [#/sec]
Requests per second 2:   89.32 [#/sec]

MyRouter extends Zend_Controller_Router_Rewrite (40 routes)

Server Hostname:         www.zend-test.com
Document Path:           /blog/2008/07/14/test
Requests per second 1:   89.62 [#/sec]
Requests per second 2:   89.41 [#/sec]

Documentation

About these ads

Written by Federico

July 15, 2008 at 12:27 am

27 Responses

Subscribe to comments with RSS.

  1. How did the performance differ with your solutions?

    Dave Marshall

    July 15, 2008 at 8:05 am

  2. If I extend Zend_Controller_Router_Rewrite:

    ab -kc 80 -t 60 http://www.zend-test.com/blog/2008/07/14/test

    4 routes: 89.47 [#/sec]
    20 routes: 89.32 [#/sec]
    80 routes: 89.62 [#/sec]

    Because I’m using multiple configuration files, the total amount of routes does not affect the performance of the routing process when the request is routed to the blog module.

    federico

    July 15, 2008 at 8:35 am

  3. Why do you need so many custom routes? I never found any use for them myself. I find the default route to be a good solution for 99% of the cases.

    Eran Galperin

    July 15, 2008 at 3:27 pm

  4. What isn’t clear is what position the blog route has in your route stack. Those figures make me think it’s the first route checked that matches. What’s the worst case scenario, where the last of 20 routes matches and all 20 ini files have to be loaded?

    Nathan Wright

    July 15, 2008 at 6:25 pm

  5. @Eran: I personally don’t like having extraneous words in my url just so my routing can be simple. If I think of /book/view/id/10, the words id and view add no value and just make the url less guessable. I therefore prefer to explicitly map out every url on my site, exposing no information about controllers/actions to the user.

    Thanks for the article, Federico. Gonna hook this up right away :)

    Neil Garb

    July 15, 2008 at 8:02 pm

  6. Nathan: I think what you’re missing here is that the first part of the URL (the “/blog” part) is actually what defines which ini file has to be loaded, so you don’t need to load all 20 files.

    Seb Barre

    July 15, 2008 at 8:10 pm

  7. Hi Nathan,

    If you use Zend_Controller_Router_Rewrite, you have to add 10, 20 or 40 routes to the config.ini file. And the router has to iterate through 1/40 routes until it finds a positive match.

    But, if you split the file:

    blog.ini (5 routes)
    shop.ini (5 routes)
    gallery.ini (5 routes)
    forum.ini (5 routes)

    The router always iterates through 1/5 routes.

    If you have 20 modules, and each module has 3 to 5 routes, the order of the routes or the popularity of the module (or URI) is irrelevant.

    federico

    July 15, 2008 at 8:18 pm

  8. @Eran

    Why do you need so many custom routes?

    For security and maintainability reasons. Using magic numbers to retrieve parameters from the request object can lead to a maintenance nightmare. I’ve seen this done many many times, for example: getParam(1)

    federico

    July 15, 2008 at 8:33 pm

  9. Good stuff Federico, will bear this in mind if I ever start using lots of custom routes.

    Dave Marshall

    July 15, 2008 at 8:44 pm

  10. I am wondering how easy this would be to modify to have it look inside the module directory for say a routes.ini file that way you can keep the module all centralized.

    I’ll check it out to see what i come up with.

    Jon

    July 16, 2008 at 1:18 am

  11. [...] Improving the Performance of Zend Controller [...]

  12. @Jon: Yes, another advantage of doing it this way is that you can create self-contained modules:

    blog/
    |-- controllers/
    |-- i18n/
    | `-- en/
    |-- models/
    |-- properties/
    | |-- BlogConfig.php
    | |-- BlogRoutes.php
    | `-- BlogDependencies.php
    `-- views/

    You can read more about this here.

    phpimpact

    July 16, 2008 at 9:04 am

  13. Django does something similar to this. Each Django app can have its own urls.py. As said before it makes the modules more self-contained.

    Eric J

    July 16, 2008 at 7:48 pm

  14. True. Django’s routing process is much faster than Zend’s. Of course, it compiles the regular expression, but the design is more flexible and scales better.

    federico

    July 20, 2008 at 1:58 pm

  15. [...] Cargnelutti расказал как увеличить производительность Zend_Controller при помощи [...]

    planet-php.ru

    July 22, 2008 at 11:41 am

  16. [...] Recent benchmarks show that the Zend Framework is slower than other Web frameworks. Another issue, explained here, is the design of the Router. If an application increases in complexity and attracts more traffic, [...]

  17. I am wondering if you have ever though about making this a proposal in the ZF wiki (http://framework.zend.com/wiki/display/ZFPROP/Home)? I think this would be a good feature too have available in a stock install of ZF.

    Jon

    July 30, 2008 at 11:42 pm

  18. I posted a message on Ben’s blog. He’s the one updating the router.

    Federico

    July 30, 2008 at 11:56 pm

  19. [...] this: Improving the performance of Zend_Controller PHP::Impact ( [str Blog] ) Maybe You are also using ajax which initiates framework on every [...]

  20. This is very similar to the method I am working on albeit with a different motivation; I am looking to modularise the routes, as opposed to improve performance.

    A problem I have encountered however, is that because only the routes matching the first section of the URL are loaded, reversing routes to generate URLs for other sections doesn’t seem possible.

    eg. You can’t generate a URL for something in gallery/… from within the blog because the gallery routes don’t exist.

    Of course you can write the URL manually, but that negates what I see as one of the more useful parts of the routes.

    Brenton Alker

    August 3, 2008 at 1:20 pm

  21. Hi Brenton, here is a way of encapsulating routes into modules.

    Federico

    August 3, 2008 at 8:56 pm

  22. How many routes do you expect there to be for a large website? If that’s 40 the current implementation may not be the fastest but should be fast enough to handle matching each of those.

    Koen

    August 22, 2008 at 4:13 pm

  23. Hmm, that’s a tricky one, considering that a module can have n amount of routes, and an application n amount of modules.

    Federico

    August 22, 2008 at 8:05 pm

  24. [...] Improving the performance of Zend_Controller [...]

  25. [...] Improving the performance of Zend_Controller Posted by Federico Filed in Frameworks, PHP [...]

  26. [...] to implement the default Front Controller? What if you don’t need all that flexibility, a URL mapper, a ViewRendered plugin or an ActionStack helper. Maybe all you need is just a couple of [...]


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 1,033 other followers