0. Database optimisation

Purpose : make sure the database (and the queries to it) run as fast as they can

If you have a large site and/or a large number of visitors, you may want to add the following indexes to your .71x tables : (in MySQL syntax)

    alter table nuke_comments add index idx_pid (pn_pid);
    alter table nuke_comments add index idx_sid (pn_sid);
    alter table nuke_group_membership add index idx_ug (pn_uid,pn_gid);
    alter table nuke_message add index idx_exp (pn_active,pn_expire);
    alter table nuke_modules add index idx_name (pn_name);
    alter table nuke_poll_data add index idx_pollid (pn_pollid);
    alter table nuke_pollcomments add index idx_pollid (pn_pollid);
    alter table nuke_referer add index idx_url (pn_url);
    alter table nuke_stats_date add index idx_date (pn_date);
    alter table nuke_session_info add index idx_last (pn_lastused);
    alter table nuke_stories add index idx_catid (pn_catid);
    alter table nuke_stories add index idx_topic (pn_topic);
    alter table nuke_user_data add index idx_uid (pn_uda_uid);
    alter table nuke_userblocks add index idx_ub (pn_uid,pn_bid);
    alter table nuke_users add index idx_uname (pn_uname);

and perhaps some additional ones depending on the modules you're using.

Tuning your DBMS for performance, and regular maintenance of your database (e.g. 'OPTIMIZE TABLE ...' of your larger tables in MySQL) is also important to improve query times.

Tests with a large PostNuke .713 site on small test server showed that when database queries form a major bottleneck, adding a single index to a large table already improved response times on the homepage by 22% for anonymous visitors (from 1315 ms to 1025 ms), and by 59% for registered users (from 2750 ms to 1135 ms).

1. Query caching

Purpose : avoid unnecessary queries to the database for the same values

The database abstraction layer PNADODB used in PostNuke can be configured to cache some query results, so that it doesn't need to execute the same query again.

Better of course is to avoid doing unnecessary queries in the code in the first place, by using static variables or class variables to temporarily store frequently requested values during script execution.

Adding a few well-placed variables in the code gave an another improvement on performance of about 34% (from 1025 ms to 676 ms) for that same homepage, or an overall improvement factor of 49% on the database level. (The few changes mentioned above are being included in the PostNuke code, so this will be used as starting point to evaluate other ways to improve performance.)

Before trying out any of the other performance tips below, it is therefore worthwhile to make sure that a few large tables in your database aren't really the main bottleneck for your current website.

2. Code optimisation and code caching

Purpose : make sure the code runs as fast as it can, and avoid recompilation of scripts

ZendOptimizer is a free PHP add-on from Zend (http://www.zend.com) that tries to optimize your PHP scripts after compilation to speed up processing. Tests with the same large PostNuke .713 site showed an improvement of about 7% in average response times (from 676 ms to 626 ms) for the homepage. Other parts of your site may show better optimization results.

ZendAccelerator is a $$$ PHP add-on from Zend that not only tries to optimize the compiled code, but also caches it in memory so that it doesn't need to be recompiled at each page request. Tests on the same homepage gave an improvement of about 30% in average response times (from 676 ms to 469 ms). The ZendAccelerator GUI showed a so-called speed-up factor of x 4.52 for 90% of the files, and of x 15.81 for 75% of the files, but as you can see, the actual performance gain for the overall homepage isn't quite that high...

APC Cache (http://apc.communityconnect.com/) provides a similar functionality as ZendAccelerator, but is available for free. Other benchmarks have reported that it doesn't perform quite as well as its $$$ competitor, but it should still give you an interesting performance boost (assuming you have memory to spare).

The PHP Accelerator (http://www.php-accelerator.co.uk/) is also available for free, and gave an improvement of response times of 31% (from 676 ms to 466 ms), comparable to that of ZendAccelerator. Source code is currently not available, though.

afterBURNER*Cache (http://afterburner.bware.it/cache.htm) is another comparable free product worth looking at. Source code is available.

On a somewhat related matter, Smarty and some other template engines allow you to "compile" your templates into PHP code, which will also speed up their processing. You can combine this with the products above to achieve better performance.

3. Block input/output caching

Purpose : avoid re-creating a block when its content hasn't changed

Some template engines also allow you to cache the HTML output of particular templates (e.g. blocks). The same functionality can also be applied to the PostNuke code, before or after templating - see Caching Strategy.

Block caching (and page caching) tends to produce higher levels of improvement than other optimizations : 55% for that same homepage (from 676 ms to 300 ms).

4. Page caching

Purpose : avoid re-creating the whole page when nothing on it has changed

There are some easy-to-use PHP classes available to introduce page caching on your website - PEAR Cache/Output.php and jpcache are two examples. However, a "PostNuke-aware" caching system that can differentiate according to users and modules would be a better approach - see Caching Strategy about what could/should be cached.

Page caching gave a 59% improvement for the same homepage (from 676 ms to 279 ms). When you combine this with code optimization/caching to lower the "fixed cost" of PostNuke execution, performance gains become really interesting, with 84% improvement for that homepage (from 676 ms to 110 ms) with ZendAccelerator, or 82% improvement (from 676 ms to 120 ms) with the free PHP Accelerator.

At this point, further improvement can be achieved by going back to database optimisation to reduce the median response time (around 50 ms here) for cached pages, and by revising your Caching Strategy to increase the cache hit rate (90% here) even more.

Or if you're about to get slashdotted, you can always generate static HTML pages for your most heavily hit pages, in which case local response times get down to 1.4 ms and you've just achieved an improvement of 99.8% compared to your original website. :-)

5. Output compression

Purpose : send less bytes over the network

Not quite related to PostNuke itself, but relevant for the overall performance of your website as seen by your visitors is the possibility to compress the HTML output before sending it to the browser.

PHP itself supports compression via ob_start("ob_gzhandler"), and Apache has mod_gzip to handle compression. If you want to include your own compression, some care must be taken to handle the compression correctly for output, though - the page caching classes PEAR Cache/OutputCompression.php and jpcache cover this already.

6. 304 Not Modified header

Purpose : avoid sending back the whole page when it hasn't changed

The final (ultimate ?) performance booster for your users is when they don't have to actually retrieve the same page again when it hasn't changed. By taking care of sending adequate HTTP headers (with the PHP header() function) and checking the browser request for If-Modified-Since headers, you can avoid processing the request altogether and simply send back a 304 Not Modified header.

That means the browser cache (and any intermediate caches) can finally do their job, and your server can concentrate on generating new content, not on generating the same content over and over again...

The page caching classes mentioned above already cover this in part, but ideally a "PostNuke-aware" expiration policy should be included in future versions.

7. Other improvements

To be continued :-)

PostNuke Performance Analysis : Site Statistics - Performance Tips - Application Profiling - Caching Strategies