Learn / eZ Publish / eZ Publish Performance Optimization Part 3 of 3: Practical Cache and Template Solutions

eZ Publish Performance Optimization Part 3 of 3: Practical Cache and Template Solutions

The first article introduced some basic performance terminology and discussed tools for benchmarking your site's performance. The second article focused on the debug output feature and some related performance solutions, such as using the viewcache and eliminating redundant database queries.

There are numerous caching options with eZ Publish. This section gives an overview of the different cache types, what they do and when you should use them.

Caching elements in the template file

The following diagram shows a typical pagelayout.tpl main template file. Its elements can be divided into the template cache and the content view cache.

Template cache

The cache-block mechanism makes it possible to reduce the processing time of the main template, which often contains a lot of dynamic elements. It is used to instruct the system to store and reuse cached blocks of template code based on different conditions. The cache-block mechanism is described later in this article.

Content view cache

Recall from the second article in this series that the viewcache stores the HTML/XHTML output from a node view the first time it is accessed (and is thus dynamically generated). For subsequent page requests, eZ Publish serves the particular page from the viewcache, reducing server load and page load times. eZ Publish automatically clears the cache when new content is published or modified.


When this setting is enabled, the system generates the viewcache when an object is published, as opposed to when it is first accessed. Enabling PreViewCache can slow down the publishing process, but it is useful on busy sites to ensure that an uncached article cannot be accessed at the same time by several people (as is the case with the viewcache); it would have been cached before the first article view. PreViewCache is a sub-setting for the viewcache, and therefore only has an effect if ViewCaching is enabled. For maximum performance on a production server, template cache and viewcache should always be enabled. Below are the recommended configuration settings:



TemplateCompile and TemplateOptimization will be explained in the template section in the second half of this article.

The static caching feature is available in eZ Publish 3.6 and higher. It uses Apache rewrite rules to check if a static file exists, then serves it straight from disk (and thus avoids using the PHP processor). This means that the complete page can be stored as an XHTML file. In the case that the file does not exist, the request is simply sent through to eZ Publish, where there could still be cached elements in the template file as described above. Using the static cache can lead to greatly improved performance, but it restricts some functionality. It is normal to run the static cache in combination with fully dynamic pages. You can run the static cache on part of your site if:

  • it is not personalized
  • it does not include dynamic elements (such as displaying the current time)
  • it is publicly available and not restricted by permissions

Since most eZ Publish sites use one or all of the features above for at least part of the site, you can benefit by using the static cache for those parts. A typical scenario is:

  • Company information: static
  • Press releases: static
  • Webshop: dynamic
  • Restricted partner section: dynamic
  • Community forums: dynamic

Static cache with one siteaccess

In this situation, there is one siteaccess (in this example, ez.no) for which we want to implement static caching. First, modify the Apache VHOST configuration to include the rewrite rules below. You should only add rewrite rules for the VHOSTs for which you want to have static caching. In reality, this means that for each siteaccess that you want to cache, you need a different VHOST block in the Apache configuration.

RewriteEngine On   
RewriteCond /dat/ez.no/static/index.html -f 
RewriteRule ^/$ /static/index.html [L] 
RewriteCond /dat/ez.no/static/index.html -f 
RewriteRule ^$ /static/index.html [L]   
RewriteCond %{REQUEST_METHOD} !^POST$ 
RewriteCond /dat/ez.no/static$1/index.html -f 
RewriteRule ^(.*)$ /static$1/index.html [L]   
RewriteRule !\.(gif|css|jpg|png|jar|ico|js)$ /index.php

You will need to change /dat/ez.no to the path corresponding to the root location of your eZ Publish installation. In settings/override/site.ini.append.php, add the following settings to enable the static caching process:


In settings/override/staticcache.ini.append.php, configure the details about the host and the pages to cache:

# A list of url's to cache 

HostName is the host where the pages are viewed. The static cache feature uses this to retrieve the generated content to store as cache files. StaticStorageDir is the directory where the static cache files are stored. It is relative to the root directory of your eZ Publish installation. This needs to match the part between /dat/ez.no/ and $1/index.html in the rewrite rules above. MaxCacheDepth is the maximum number of directory levels to cache, as seen from the root of your installation. CachedURLArray specifies the parts of your site that are allowed to be statically cached. Use / or /products to cache only one page (in this example, the front page and the /products page). You can also use wildcards; for example, with "/products*" all URLs below /products are cached.

Static cache with multiple siteaccesses

In this example there are two site languages: English and French. The siteaccesses are called news_en and news_fr. In this case, we use the following rewrite rules, where the root directory of the eZ Publish installation is /home/httpd/ez-3.6:

RewriteEngine On 
RewriteLog /tmp/rewrite 
RewriteLogLevel 4   
RewriteCond /home/httpd/ez-3.6/static/news_en/index.html -f 
RewriteRule ^/$ /static/news_en/index.html [L] 
RewriteCond /home/httpd/ez-3.6/static/news_en/index.html -f 
RewriteRule ^$ /static/news_en/index.html [L]   
RewriteCond /home/httpd/ez-3.6/static/news_fr/index.html -f 
RewriteRule ^/$ /static/news_fr/index.html [L] 
RewriteCond /home/httpd/ez-3.6/static/news_fr/index.html -f 
RewriteRule ^$ /static/news_fr/index.html [L]   
RewriteCond %{REQUEST_METHOD} !^POST$ 
RewriteCond /home/httpd/ez-3.6/static$1/index.html -f 
RewriteRule ^(.*)$ /static$1/index.html [L]   
RewriteRule !\.(gif|css|jpg|png|jar|ico|js)$ /index.php

The following settings need to be specified: In settings/override/site.ini.append.php:


In settings/siteaccess/news_en/staticcache.ini.append.php:


In settings/siteaccess/news_fr/staticcache.ini.append.php:


Make sure that the part after static is the same as the name of the siteaccess! You can of course change static to something else, but make sure it is the same as in the rewrite rules. In settings/override/staticcache.ini.append.php, we then configure the static cache mechanism:

# A list of url's to cache 

This caches the /news and /weblog subtrees of the site on the host localhost with a maximum folder depth of 4.

Generating cache files

Static cache files are created in two ways:

  1. by publishing an object
  2. by running the makestaticcache.php script

The bin/php/makestaticcache.php script generates all the static cache files for a specific siteaccess. In our example with ez.no, use the following command to generate the cache files:

php bin/php/makestaticcache.php -s ez.no

If you want to re-create all cache files, even the ones that already exist, use the -f parameter to force the generation of all static cache files. The command below regenerates all static cache files for both siteaccesses from our second example:

php bin/php/makestaticcache.php -f

Starting from eZ Publish 3.8, there is a feature for enabling cacheable headers. By default, eZ Publish sends headers in order to prevent proxies and browser caches from storing a cached version of the page. However, in some cases, allowing this can be desirable. You can enable cacheable headers for parts of the site in much the same way as you use the static cache. You can then, for example, place a reverse proxy in front of eZ Publish to handle all caching of pages. Squid is one such application that caches HTTP requests, subsequently serving the same request more quickly. See our article on using Squid to increase eZ Publish performance. Enable and configure cacheable headers in the site.ini.append.php file. Here is an example:

# Enable/disable custom HTTP header data.
#Set Pragma HTTP header to no-cache for whole site, except /news, and 2 levels below news.

Response headers from main page
Server: Apache/1.3.35 (Unix) PHP/4.4.2
X-Powered-By: eZ publish
Expires: Tue, 16 May 2006 07:16:13 GMT
Cache-Control: no-cache, must-revalidate
Pragma: no-cache
Last-Modified: Tue, 16 May 2006 07:16:13 GMT
Content-Language: en-GB
Keep-Alive: timeout=15, max=99
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html; charset=utf-8
Response headers from /news folder
Server: Apache/1.3.35 (Unix) PHP/4.4.2
X-Powered-By: eZ publish
Expires: Tue, 16 May 2006 07:15:47 GMT
Cache-Control: no-cache, must-revalidate
Last-Modified: Tue, 16 May 2006 07:15:47 GMT
Content-Language: en-GB
Keep-Alive: timeout=15, max=87
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html; charset=utf-8

As you can see, the Pragma HTTP header no-cache does not exist in the response headers from the /news folder. Using the configuration file, you can easily control which headers should be used for various parts of the site.

The goal of smart viewcache cleaning is to provide flexibility when cleaning the viewcache of an object's nodes. It is aimed at extending the default behavior when cleaning the cache in specific situations.


SmartCacheClear should be used with a set of custom rules. Enabling SmartCacheClear can actually slow down the publishing process due to the need to apply the custom rules. Here are two practical examples to demonstrate why smart viewcache cleaning should be enabled and configured with additional rules.

Example 1 - Forum

Assume that we have a content structure like this:

eZ Publish(nodeID = 2, class_identifier = folder) | |-- Forum folder(nodeID = 58, 
class_identifier = folder)  |  |-- Interesting forum(nodeID = 59, 
class_identifier = forum)  |  |-- Topic 1(nodeID = 60, 
class_identifier = forum_topic)  | |  | |-- Reply1(nodeID = 62, 
class_identifier = forum_reply)  | |  | |-- Reply2(nodeID = 126, 
class_identifier = forum_reply)  | |  | | -- Reply3(nodeID = 127, 
class_identifier = forum_reply)  |  |-- Topic 2(nodeID = 61, 
class_identifier = forum_topic)

Users can add replies to Topic 1. By default, when SmartCacheClear is enabled and a user adds a new reply to Topic 1, the viewcache of this reply and its parent (Topic 1) is cleared.

Case 1

Assume now that the forum name and number of posts in this forum are displayed when viewing Forum folder. By default, after a new reply is added, the cache of Forum folder is not cleared and thus the number of posts will display outdated information. To fix this, you can use the "extended" behavior of the smart viewcache cleaning system. The appropriate rule is as follows: if a new forum_reply is added to forum, clear the cache of the parent of forum. In this case, the following lines should be added to the viewcache.ini.append.php file:


For example, if you add the Reply3 object, the nodeID path is: /1/2/58/59/60/127. Then, the smart viewcache cleaning system will search for nodes where class_identifier is equal to one of the values in the DependentClassIdentifier variable. The search is conducted in reverse order: the node with nodeID=60 will be checked first, then the node with nodeID=59. The node with nodeID=60 doesn't belong to the forum class, so its cache remains untouched. The next node has nodeID=59 and belongs to the forum class, so its cache is cleared. The other nodes in the path (58,2) do not belong to the forum class, so their caches remain untouched.

Case 2

Assume now that in addition to Case 1 we have a folder named Common folder, published under the eZ Publish folder and containing Forum folder as a related object. Since the Forum folder cache is cleared when a new forum_reply is published, the cache of Common folder has to be cleared as well. For this purpose, we need to add another rule to viewcache.ini.append.php:


Now when a new forum_reply object is created, the cache for Forum folder and Common folder need to be cleared. There are two folder nodes in the path: one with id=2 (eZ Publish) and one with id=58 (Forum folder). But we do not need to clear cache for the eZ Publish folder (nodeID=2). The MaxParents setting fixes this: folder objects that are more than one node away from the forum node are ignored. The two rules from Case 1 and Case 2 can be combined and replaced with the following rule:

#If 'Forum folder' has object_id=56 for example then the rule above can 
be more hard by adding next lines: ObjectFilter[] ObjectFilter[]=56

The lines above restrict the rule to one folder object that has object_id=56.

Example 2 - Image gallery

Assume that we have a content structure like this:

eZ Publish(nodeID = 2, class_identifier = folder) | |-- Gallery(nodeID = 58, 
class_identifier = gallery)  |  |-- Image 1(nodeID = 59, 
class_identifier = image)  |-- Image 2(nodeID = 60, 
class_identifier = image)  |-- Image 3(nodeID = 61, 
class_identifier = image)

By default, when SmartCacheClear is enabled and a user adds a new image to Gallery, the viewcache of this image and its parent (Gallery) is cleared. In our example, assume that we need to display images stored under the Gallery subtree in the full view but also in each image view. In this case, when the new image is published we need to clear the viewcache of the parent (Gallery), the new uploaded image and its siblings. Without configuring smart viewcache rules, the other images (the siblings) are outdated. The appropriate rule is as follows: if a new image is added to Gallery, clear the cache of the parent of the Gallery but also of the siblings. In this case, the following lines should be added to the viewcache.ini.append.php file:


There are several caches dealing with internal eZ Publish features that you should enable in order to improve site performance.

Role caches


As soon as a new user (registered or anonymous) visits the site, eZ Publish will check whether there is a cached file on disk containing pre-calculated user permissions. If the file does not exist, eZ Publish calculates the new permissions and saves these to a file. On sites with complex role systems, the role cache can reduce the time spent on calculating permissions. The cached file is saved on the file system under the directory var/(site)/cache/user-info. Here is a role cache file example for an Administrator user (object id 14):

user-14.cache.php:  return array ( '*' => array ( '*' => 
array ( '*' => '*', ), ), ); ?>

Translation cache


The translations of GUI labels in eZ Publish are stored in XML files for each language. When TranslationCache is enabled, the translation cache system will parse the XML translation file (translation.ts) and compile it to native PHP files for faster execution. This significantly reduces load time and should always be used. The translation cache is stored on the file system under the directory var/(site)/cache/translation/. The example below presents one node from the translation.ts file and its PHP equivalent:

translation.ts    Current location  Obecna lokalizacja     
Cached translation file: array (  'context' => 'design/admin/pagelayout',  
'source' => 'Current location',  'comment' => '',  
'translation' => 'Obecna lokalizacja',  'key' => 
'2efc78139d269b1f0ebf88225e7e20c7',  )

Template override cache


Data such as template override rules is rather static. If the template override cache system cannot find the override cache file, it will generate a new one and store it to disk. This increases performance because eZ Publish does not have to read override.ini(.append.php) files on each script invocation. The override map is stored on the file system as native PHP under the var directory (var/(site)/cache/override/). One override cache file is generated per siteaccess.

eZ Publish has its own powerful template language. This section looks at a few cases where you can improve site performance by optimizing the templates.

What not to do in templates

The eZ Publish template language should not be treated as a general-purpose programming language. When application logic is added to the template itself it reduces website performance. An example of sub-optimal logic in templates is:

  1. Fetch all articles and users to arrays.
  2. Use a foreach template construct to iterate through all articles to find out which authors have written which articles.
  3. Display the result with another foreach construct for each article.

This scenario is not desirable for the following reasons:

  • Fetching many objects takes up a lot of memory.
  • Fetching large object lists is slow.
  • Doing compare logic on large object sets in the template language is much slower than in PHP or in SQL.

It is bad practice to have logic like this in the template itself. In cases where you find yourself adding more and more logic to the template code, ask yourself whether it can be implemented as a separate module, template operator or as an extension to eZ Publish. Doing the example above in pure SQL with a template operator is many times faster than doing it in the template.

Template compilation


The template language is parsed and executed by eZ Publish. Since this task can be quite time-consuming, there is a feature in eZ Publish to convert templates to pure PHP code. This makes execution quite a lot faster. The TemplateCache setting enables cache-blocks (as explained on the next page) and template compilation to be used. TemplateCompile controls whether template compilation should be done and TemplateOptimization attempts to optimize the PHP in these files.

Custom template operators

For better performance, you could replace complex pieces of template code with custom operators that do the same thing in PHP code. Here is an example of a complex piece of template code that will return some redundant data:

{def $article_list=fetch( content, tree, hash( parent_node_id, $node.node_id ) )  
{foreach $article_list as $article}
  {if $main_node_ids_array|contains( $article.object.main_node_id )|not}
    {set main_node_ids_array=$main_node_ids_array|append( $article.object.main_node_id )}
    {set unique_article_list_array=$unique_article_list_array|append( $article )}  

{foreach $unique_article_list_array as $unique_article}
  {node_view_gui view=line content_node=$unique_article.object.main_node} 

Assume now that we need to display a drop-down list with the names of a hundred objects stored under a folder somewhere in the content structure. Instead of fetching all objects, we can use a custom template operator to return the SQL query result as an array with object names. Here is an example SQL query for use in a template operator:

SELECT ezcontentobject.name, ezcontentobject.id, 
FROM ezcontentobject_tree, 
ezcontentobject, ezcontentobject_name 
WHERE path_string LIKE '/1/124/%' 
AND depth 2 
AND node_id !=124 
AND ezcontentobject_tree.contentobject_id = ezcontentobject.id 
AND ezcontentobject_tree.contentobject_id = ezcontentobject_name.contentobject_id 
AND ezcontentobject_tree.contentobject_version = ezcontentobject_name.content_version 
AND ezcontentobject_name.content_translation = 'eng-GB' 
ORDER BY ezcontentobject.published DESC

This returns all object names and their ids where the parent node id is 124. Then, in the template we can use the following code:

{foreach objectsqlnames() as $object}  

With 100 objects, the code above can be three times faster than code with the regular fetch function. It requires two fewer SQL queries and returns only the data that is needed.


If you need to display some information about node children or grandchildren in the node template, this is possible with $node.children and $node.children.0.children. Instead of grabbing content with the fetch function like this:

{def $children=fetch( 'content', 'list', hash( 'parent_node_id', $node.node_id ) )} 
{foreach $children as $child}

...display child data as follows:

{foreach $node.children as $child}

This requires two fewer SQL queries and thus reduces the page load time.

Limit, offset

When fetching objects you should always use limit and offset in order to control the records that are returned. Some queries return a lot of redundant records if you do not specify the number of results to return (limit) and the record where the return count (offset) should start. Here is an example fetch statement that uses both limit and offset.

{fetch( 'content', 'list',  hash( 'parent_node_id', 2,  
'limit', 15,  
'offset', 10,  
'only_translated', true(),  
'language', 'ger-DE' ) )}

An optimized pagelayout template should generate only two or three SQL queries with viewcache enabled. When building a site, you normally have several dynamic elements in your main template pagelayout.tpl. This template will then use most of the total processing time for your site, which is undesirable. To limit this processing time, you can use the cache-block template function.

How it works

The cache-block function stores the result of dynamic template code in a plain HTML/text file to be loaded the next time the same code is requested. For example, a navigation menu usually consists of a list of folders and / or objects. This menu is often the same for most or all pages. You can use a cache-block to store the result of the dynamic code. Consider the following main template file (pagelayout.tpl) example:

<a href="http://december.com/html/4/element/html.html"><span><html></span></a>

<a href="http://december.com/html/4/element/head.html"><head></a>
<a href="http://december.com/html/4/element/link.html"><link</a> rel="stylesheet" type="text/css" href={"stylesheets/core.css"|ezdesign} />
<a href="http://december.com/html/4/element/link.html"><link</a> rel="stylesheet" type="text/css" href={"stylesheets/debug.css"|ezdesign} />
{include uri="design:page_head.tpl" enable_glossary=false() enable_help=false()}
<a href="http://december.com/html/4/element/body.html"><body></a>
{include uri="design:page_toppath.tpl"}
{include uri="design:left_menu.tpl"}
{include uri="design:page_copyright.tpl"}

In this example, we have added a cache-block around the left menu navigation code. This means that the first time the page is loaded, this code is executed and the result is stored in a text file. During successive pageloads, this text file is loaded and no left menu code is executed. This happens until the cache-block expires (which is by default two hours).


The cache-block function takes four parameters: keys, expiry, ignore_content_expiry and subtree_expiry.


The keys parameter is used to define the uniqueness of the cache-block. By default, eZ Publish uses the template name and position of the block as keys. This means that if the cache-block is common for all occurrences of a given template (normally pagelayout.tpl), you do not need to specify any keys. An example of this would be a menu that does not change even for different users or different areas of the site. If you need to specify a key you can use either a single variable or an array, as shown in the following example with cache-blocks that are unique for each URL:

{cache-block keys=$uri_string} ... tpl code {/cache-block}

Here is an example with cache-blocks that are unique for each URL and user:

{cache-block keys=array($uri_string,$current_user.contentobject_id)} 
... tpl code {/cache-block}


If you do not specify the expiry parameter, the cache-block will automatically expire in two hours or if new content is published. If this expiry does not fit your needs you can specify the expiry time manually in seconds:

{cache-block expiry=120} ... tpl code {/cache-block}

Ignore content expiry

Sometimes you do not want your cache-blocks to expire when content is published. For example, a footer containing copyright information is not affected by new content and thus does not need to expire. With the ignore_content_expiry parameter you can disable the expiration when content is published:

{cache-block ignore_content_expiry} Cached content, even if an object is published. {/cache-block}

In between the default policy of always expiring the cache-blocks when content is published and the functionality of ignore_content_expiry is the subtree_expiry parameter. With this parameter, you can control the expiration of a cache-block when content in a specific subtree (like /products) is published; there might be a block inside the pagelayout template containing a list of the five latest products. In the following example, the cache-block will expire when there is something published in the /products/bargain subtree or after 30 minutes:

{cache-block expiry=1800 subtree_expiry=/producs/bargain} ... tpl code {/cache-block}

Usage tips

Since there is some processing involved in using the cache-block itself, you should use as few cache-blocks as possible and make them unique using keys. It is often very efficient to have two large cache-blocks that will cache all header / title / path information and footer information. This can be used in combination with a nested menu cache-block as in the example below:

{cache-block keys=$uri_string}   

rel="stylesheet" type="text/css" href={"stylesheets/core.css"|ezdesign} /> rel="stylesheet" type="text/css" href={"stylesheets/debug.css"|ezdesign} /> {include uri="design:page_head.tpl" enable_glossary=false() enable_help=false()} {include uri="design:page_toppath.tpl"} {cache-block} {include uri="design:left_menu.tpl"} {/cache-block} {/cache-block} {$module_result.content} {cache-block} {include uri="design:page_copyright.tpl"} {/cache-block} Note that when using nested cache-blocks, the outermost block will not know if a sub cache-block has or should have expired. As a result, the outermost block should have a shorter expiry than the sub-block.

Here we will describe two settings that speed up the Administration Interface and a miscellaneous setting pertaining to images.

Delayed indexing

By default, eZ Publish instantly indexes objects for the built-in search engine upon publishing. This process is quite time consuming, especially for larger documents. On many sites, it is acceptable if the search indexing is done afterwards. When the setting DelayedIndexing is set to "enabled" (in site.ini.append.php), eZ Publish will put newly published objects in a queue for indexing at a later time. This will reduce the time that content editors have to wait when publishing a document. In order to start this process you will need to enable the cron job indexcontent by adding the following to cronjob.ini.append.php:


Content structure menu

The Administration Interface contains a left menu that displays the node tree. This menu can slow the Administration Interface on larger sites. Usually it is enough display container classes, such as Folders and User groups, to the second level of tree depth.


These settings are optimized by default in the 3.8 and higher versions of eZ Publish, but if you add new classes you may need to do additional configuration.

Images and Exif data

Images uploaded to eZ Publish are automatically scaled and converted as needed. Images taken with digital cameras contain Exif information about shutter speed, focal length and so on. This information can be stripped out if you do not need it, in order to make your images smaller, use less bandwidth and load faster. ImageMagick is the default eZ Publish image processor, and it copies all Exif data unless otherwise specified. eZ Publish makes it easy to add filters to remove Exif data for different images. Simply add the following line to the particular siteaccess' image.ini.append.php file:

Filters[]=strip=+profile "*" +comment   


This will strip Exif information for "small" image variations and can be added to others by simply adding the Filters[] line to the corresponding variation's definition. Newer versions of ImageMagick have a -strip flag that will also remove Exif data. To use this, replace the [ImageMagick] section with:


This can reduce overall page size and speed up page load times, especially for sites with many small images.

Throughout this series, we have focused on configuration and setup changes that can be done within eZ Publish. This does not include the hosting environment, typically consisting of Apache, MySQL, PHP and APC, which is also important for eZ Publish performance. See the related articles about the server environment, MySQL, clustering and server architecture for information about tuning the hosting environment. This article concludes our three-part series on eZ Publish performance optimization. The first article introduced basic performance terminology and discussed tools to benchmark your site's performance. The second article outlined the eZ Publish debug output features. This article explored caching, templating and some miscellaneous settings to optimize site performance. We thank the eZ community for continuing to share tips and experiences about eZ Publish performance optimization and invite you to add comments and tips to this article.