Now Playing Tracks

Silverstripe caching

In this post we’re going to cover how silverstripe’s caching works. There are two ways to cache, both have pros and cons.

Static caching
Quite simply static caching saves the HTML output via php to a static HTML file. Silverstripe then monitors (via some functions you create/edit) when content is updated so that if an HTML file in the cache becomes out of date it can be regenerated. As the cached file is a complete HTML page it means performance is incredibly fast. Rather than hundreds of milliseconds or even seconds of php execution the page now the site will respond is less than 20 milliseconds. Fast.

There are drawbacks though. There is one cache per page which means dynamic data can’t be used. This means things like random quotes ( = no longer random), member details, or ajax requests aren’t gonna work.

Controlling what is and isn’t served via the static cache is relatively easy. There are three functions, allPagesToCache, subPagesToCache and pagesAffectedByChanges. The first function controls which pages to cache, the second two control which other pages to re-cache if this page changes.

Notice in the allPageToCache function I’m not caching form pages. This is because Silverstripe generates a unique code for each form to check whether the form request has been generated from a form, or if the page is being accessed via a CSRF attack. If the page is cached the code stays the same and Silverstripe thinks every form submission is a CSRF attack.

The subpages to cache is normally pretty straightforward as you know which pages these are and how and if they will be affected by their parent changing.

The third function can become very complex. Especially if you pull content from one page into the next. For example if a parent contains it’s child’s content, like a list of child pages with a summary. This means as opposed to the second function when the child changes the parent’s cache must be updated. Another example would be a homepage with a news list. Every time a news page is created or updated we need to re-cache the homepage.

So it can become tricky but for some pages very useful. So how do you turn it on? Firstly in mysite/_config.php add Object::add_extension(“SiteTree”, “FilesystemPublisher(‘cache/’, ‘html’)”); then in your .htaccess file change the redirect line from RewriteRule .* sapphire/main.php?url=%1&%{QUERY_STRING} [L] to RewriteRule .* sapphire/static-main.php?url=%1&%{QUERY_STRING} [L]. This will now re-route all requests to first look in the cache directory for the page and if that fails go through the normal request director.

Partial caching
Unlike static caching which caches the entire page, partial caching on caches a certain part, like a header, or a footer, or the sidebar of the page.

This does not have the same performance benefits of static caching. It continues to go through the standard request director however when the request comes to render the page, partial caching allows it to render chunks of html without having to access variables in the model or extra objects.

So which parts make the most difference to how long if takes to render a page? Menus. While the page you’re currently on is already loaded, menus must find a bunch of other pages and load them from the database, this is an overhead that partial caching can reduce and speed rendering up.

Above is the partial cache code I put around my navigation. As it’s only part of the page each partial cache piece must have a unique name, which also controls the when the regeneration happens. So this partial cache piece starts with ‘Navigation’, nothing special, I may also have a ‘Header’ one or ‘Footer’ one, call it what you like. The next parts after that tell the template renderer on which conditions it can use a pre-existing cache and when it needs to generate a single new partial cache piece.

Firstly ID parameter makes sure I have a partial cache for each page, so that the menu’s current/section states are correct. Without this every page would use the same piece and the menu states wouldn’t change to the current page.

Aggregate(Page).Max(LastEdited) tells the template renderer to check when ANY page was last edited. Surely we can just search when the top level pages were just edited? Yes you can, however as Silverstripe caches the result of queries for a request this query can be used over and over again in different partial caching pieces without needing slightly different queries.

Aggregate(SiteConfig).Max(LastEdited) makes sure the SiteConfig hasn’t changed since we generated the last cache. Particularly useful if you’ve extended SiteConfig, using variables from it like SiteConfig.Title or want to cache your head tag (do it).

If you have other DataObjects which aren’t pages and you’re using their properties in a page makes sure you add an aggregate with their class name. Aggregate(ClassName).Max(LastEdited)

Last but not least unless CurrentMember. This may need to be more complicated if you have a member’s area, however if the only members you have are CMS users then this saves your arse. To explain this let me tell you what would happen if you don’t include this. Your partial cache piece is created. Your CMS user logs in, changes some part of the page which is used in your partial cache. They click on ‘View draft site’, they would see NOTHING had changed. This is because they haven’t published the change so the cache still thinks it relevant. Adding it removes this issue.

You need to really think about how to write a partial cache key, if you get it wrong (excluding something that could change) then it will result in the page’s output not being updated correctly. Take your time, go through and check all the things you’re accessing.

Adding a partial cache to a page will actually make the page slower, as it has to do extra queries in the partial cache’s key. However this is only true on the first time a page is viewed, after that the partial cache has been created and will save on render time.

So as I said earlier use partial caches around menu’s (header, subnav, footers etc). Your html head tag is a nice thing to optimise. Lists of child pages or objects can also be sped up without too much difficulty.

Just a couple of tips here.

1) While you may be tempted to create lots of partial caches for small bits of content. Don’t. It’s better (read quicker) to have a slightly more complex partial cache key than 2 partial caches.

2) Don’t put partial caches around bits of HTML which contain no controls or $variables. The thing that partial cache is saving you is accessing those functions. It will not help static HTML at all.

3) Don’t nest partial caches. Keep it simple. It can be confusing when something hasn’t updated correctly as to why and what is responsible. It some cases <% uncached %> can help you remove complex bits from your cache.

4) When you testing your caches change <% cached ‘Navigation’ … %> to <% uncached ‘Navigation’ …%> to disable this cache.

blog comments powered by Disqus

1 note

  1. experience-ss reblogged this from pitchandtone
  2. pitchandtone posted this
We make Tumblr themes