The Cache at the End of the Rainbow
One of the strengths of modx has always been its caching mechanisms. The modx cache stores static versions of your Resources so that only the first visitor to the page will have to wait for it to be processed - in theory.
The catch is that modx is the "Infinite Configuration Engine" (ICE), which means it's so flexible, we as Users have all the power in the world to use it in a "less-than-optimized" way. Partly it has to do with a convention in the modx documentation where examples of Snippet calls are always shown uncached, for example:
[[!getResources]] <!-- uncached -->
They're demonstrating that you have control over the caching of Snippet outputs, but really there are few cases where you need to call your Snippets uncached. Mark Hamstra wrote a post with comprehensive caching guidelines here.
To summarize, we should Always Be Caching. So lets say we're being good little modxers and we call all our snippets cached and make all our resources cacheable. We've done our jobs and our sites will be as fast as possible, right? Not quite...
The vast majority of sites these days are dynamic in nature and have frequently updated content. That's the whole reason for content management systems. Well whenever a Resource is created or edited and saved, the entire MODX cache is cleared. This may seem like overkill but it's necessary, because depending on the site, one new Resource could change the content of an untold number of other pages. For example a global navigation element that lists the most recently created Resources. A site could potentially be littered with these kinds of dependencies, so MODX has no choice but to clear the whole cache by default.
But there's a way to control that behaviour on a granular level. There's always a way in MODX!
Jason Coward's getCache snippet allows you to create custom cache partitions (folders), set the expiry date on those cached files, and choose when to manually clear them so that site updates are reflected when you want them to be. He wrote a tutorial here on how to use it, but I'm going to provide a detailed use case below, based on this very blog.
As this site gets more and more posts added to it, it becomes more and more undesirable to clear the entire cache. For example, some of the dynamic content, like the Popular Posts menu, requires significant processing, and there's no benefit to re-processing every time a new post is made, because it's unlikely to change based on that one, new entry.
With all the flexibility of getCache, it's worth devising a strategy firsthand to more effectively achieve our goals. Here's an example caching strategy, based on the original version of this blog:
- Level 1 - standard MODX caching. These elements, like the Latest Posts menu, need to change whenever new content is published. I still call these Snippets cached so they don't have to process on every page request, but MODX will clear the cache when I need it to so no action required.
- Level 2 - weekly refresh. These elements, like the aforementioned Popular Posts menu, only need to be refreshed once a week, if that. The relative popularity of posts won't change drastically in a week, and even if it does it's no big deal for these elements to be out of sync for a few days.
- Level 3 - never expires. Elements like the Categories menu really don't change much at all, ever. It would be silly to waste any processing on these elements, so we will cache them forever. If that sounds risky to you, don't worry I'll show you an easy way to manage the odd times when we actually do need to refresh those cache files.
I won't go into great detail about usage, because you can get that info straight from the source here. But let me demonstrate the persistent cache partition:
[[!getCache? &element=`getResources` &cacheKey=`name_of_custom_partition` &cacheExpires=`0` &parents=`[[*id]]` &tpl=`myTpl` &siteVer=`[[++site_ver]]` ]]
Break It Down
- !getCache? » GetCache itself is always called uncached because it must determine, on every page request, if the element it's wrapping needs to be processed or not. If it does, it processes it. If not, it retrieves the output from the custom cache partition specified.
- &element » The name of the element you wish to call. In this case it's the getResources snippet, but you could call it on a chunk as well. See Jason's post for details.
- &cacheKey » The name of the custom cache partition, or folder, where you want this element's cached output to be stored.
- &cacheExpires » The time, in seconds, before the custom cache automatically expires. In this case, a value of "0" means "never expire".
- &parents » A required property for getResources. All the properties for your Snippet call, or Chunk call for that matter, should be specified here within the getCache call.
- &tpl » A required property for getResources to tell it what chunk to use as the output template.
- &site_ver » This requires some extra explaining (see below).
Clearing or Refreshing the Custom Cache
With &cacheExpires set to
0, the cache will persist "forever". This is what we want, but for those odd occasions when we want to update the cached content, the only way to do it is to manually delete the
/core/cache/name_of_custom_partition/ folder. If you're handing the site off to a client, you probably don't want that phone call six months from now "I changed this thing on the site but it won't update on the front-end".
I can share a few solutions for this, but they all have their pros and cons:
Method 1: System Setting in Properties
getCache will create a new cache file if any of the element's properties have changed. So if you change the Snippet call (the intended output) it will create a new cache file in the same location, but with a different unique name, thereby "updating" the returned content. In my example, I leveraged this by using a "made up" property "site_ver" and entering a system settings placeholder as the value. System settings are loaded first upon any request, so the value of
[[++site_ver]] will only change if I modify the site_ver system setting. All the getCache calls on the site use this setting, so if I modify it, new cache files (with updated content) will be generated. I use a version number as the value, because that doubles as a way to track major changes to the site.
- PROS: Really easy to manually "update" the content returned from custom cache files. To give your clients easy access to this setting, use Mark Hamstra's ClientConfig component.
- CONS: Your cache directory will continue to grow larger and larger every time you use this functionality because getCache is generating new cache files, NOT deleting the old ones. The solution to this would be to periodically clear the custom cache partition manually. Don't forget though - you don't want your server running out of disk space!
Method 2: Plugin to Refresh Custom Cache
This is probably a "better" method, although harder to hand off to a client. Create a new plugin, let's call it "ClearCustomCache":
$modx->cacheManager->refresh(array( 'name_of_custom_partition' => array() ));
Replace 'name_of_custom_partition' with yours, obviously. This method is available in MODX 2.1 and later (more info here). You tell it what cache partition (folder name) to refresh, and it does that. You need an event to fire the plugin, and I used "OnBeforeCacheUpdate". After you've created the plugin, simply clear the cache as usual using the Main Menu item Site » Clear Cache. Once that's done, you should disable the plugin because otherwise, there'd be no point having a custom cache partition at all - it would always refresh along with the default cache.
- PROS: Reliably clears the deprecated cache files.
- CONS: How the heck do you teach a client to enable/disable the plugin?
Method 3: Buyakah!
Put a conditional in the ClearCustomCache plugin, like this:
$ccc = $modx->getOption('custom_cache_clear'); if ($ccc) $modx->cacheManager->refresh(array('name_of_custom_partition' => array()));
Here we get the value of the system setting
[[++custom_cache_clear]], which we have to create. We can use a Yes/No type input field for the system setting for user friendliness. If the value of that system setting is "1" or "Yes" we let the plugin clear the custom cache. If not, the plugin doesn't do anything.
So you could potentially install the ClientConfig component mentioned above, and train your clients to set the value to "Yes" and then clear the cache if they want to make sweeping changes to the site, but that normally they should leave it as "No" and they'll get better site performance.
- PROS: Does what we want, and allows the client to control the caching.
- CONS: You have to remind your client to switch the custom_cache_clear setting back to "No" or all your hard work with getCache will be moot.
*UPDATE: getCache 1.1.0-pl is out! Jason's added some long-awaited TLC to getCache by adding a custom connector/processor for refreshing your custom cache partitions, essentially making these options I've described above relatively useless :P Install the new version and have fun! (note: instructions coming soon on how to add a menu item for getCache refreshment in MODX Manager :)
Hope this helps you make your MODX Revolution site a finely-tuned web-serving machine. Vive la MODX!