Partial Template Cache best practices

Greetings everyone.

The CMS Squad just rolled out some updates for the Partial Template Caching documentation.
Particularly, we’ve made a separate doc explaining the template tag mechanics in depth and refined the performance recommendations.

Some important new recommendations are:

  1. Always use cache conditionals whenever possible
  2. Avoid heavy computations in cache keys and conditionals
  3. Tune the cache backend settings

Another recommendation which is not included to the CMS documentation:

  • DO NOT cache Elemental Area. Consider caching every separate block instead (where it is appropriate). Especially if you have silverstripe/userforms installed. User data may be leaked through the cache, which is a potential security issue.

If we missed something interesting, please share your recommendations in this thread, or consider making a PR for the CMS documentation:

2 Likes

Hi,

A couple of questions / comments on the first doc:

The title for this section: silverstripe-framework/11_Partial_Template_Caching.md at 4 · silverstripe/silverstripe-framework · GitHub

Says ‘nesting in For and If…’ then goes on to talk about <% if … %> and <% loop … %> blocks. It’s a little confusing.


Updating the default cache time is a question that comes up quite often. I think a short example snippet to reinforce the information here (silverstripe-framework/11_Partial_Template_Caching.md at 4 · silverstripe/silverstripe-framework · GitHub) would be nice.


I’m happy to PR either / both of the above if it helps.

1 Like

I’m happy to PR either / both of the above if it helps.

Yes please! PRs are always much appreciated :slight_smile:

@dnsl48 thanks for updating the docs!

  1. Avoid heavy computations in cache keys and conditionals

Is there a built in way to debug this? Does the framework keep track of the overhead on each request that is spent calculating partial caching cache keys?

@dnsl48 I think there’s another important thing you could note in the docs. When I read this my heart sank:

Be careful using aggregates. Remember that the database is usually one of the performance bottlenecks. Keep in mind that every key of every cached block is recalculated for every template render, regardless of caching result.

I tested this, and it’s true. I assumed that if you used the same aggregate in a template twice it would only be calculated the first time, as that’s what I’m accustomed to when calling other methods in a template. But identical database queries are fired off multiple times.

However in my case, it didn’t matter, because I do all my cache key calculations in the controller. Because I’m using controller methods, their result is cached and reused, so I can use the same cache key fragment in multiple cache blocks without penalty.

So instead of doing this:

<% cached $List('SilverStripe\CMS\Model\SiteTree').max('LastEdited'), $List('SilverStripe\CMS\Model\SiteTree').count() %>

You can do this:

<% cached $ClassCacheKey('SilverStripe\CMS\Model\SiteTree') %>
    /**
     * Get a cache key which get the approximate state of a class.
     * Will not capture things like sort order if changes bypass the ORM.
     *
     * @param string $class
     * @return string
     */
    public function ClassCacheKey($class)
    {
        return __FUNCTION__ . md5(serialize([
            $class,
            $class::get()->max('LastEdited'), // Catch created / edited
            $class::get()->count(), // Catch deleted
        ]));
    }

And you don’t get duplicated SQL queries.

BTW in case it’s useful to others, I made a gist of the partial caching controller methods I’m currently using:

2 Likes

Is there a built in way to debug this?

No, we don’t have a dedicated debugging for template caching yet. The easiest way would be to use debugbar and see the repetitive SQL queries. However, I guess we could add extension points around the cache key generation and debug it more granularly. I’ll think about making a patch for the SS Viewer components and later we could pick it up in Debugbar.

if you used the same aggregate in a template twice … identical database queries are fired off multiple times.
However in my case, it didn’t matter, because I do all my cache key calculations in the controller. Because I’m using controller methods, their result is cached and reused, so I can use the same cache key fragment in multiple cache blocks without penalty.

That’s a good gotcha. Here’s a PR to add it to the documentation. :+1:

1 Like

Hi @JonoM - I saw your gist and was wondering, in a template how do you actually pass your data lists to the function ListCacheKey

Right now whenever I try to do that like -

<% cached $ListCasheKey($MyList) %> 

I am always getting the error -

Uncaught BadMethodCallException: Object->__call(): the method 'forTemplate' does not exist on 'SilverStripe\ORM\DataList'

Hi @obj63mc, you’re using it the way I intended (I assume I tested this but I don’t remember writing the code anymore, you know how it is :grinning_face_with_smiling_eyes:). You have a typo in your post - ListCasheKey instead of ListCacheKey - is it possible that’s the issue?

Another option is to add an extension to DataList, something like this. Then you can do e.g. <% cached $MyList.CacheKey %>

That was just a typo from me typing here, confirmed code is like -

<% cached $ListCacheKey($MyList) %> 

If I try to add a dump in the function like -

public function ListCacheKey($dataList){
        var_dump('here');
        exit;

It never gets there. Will try an extension on datalist, seems like that would work well. Biggest thing for me is trying to cut back on some of the duplicate query calls for a site I am running with like 40 different data models and thousands to hundreds of thousands of records so far.