Tag-Based Caching (Block Cache)

Last Updated: Oct 20, 2023
documentation for the dotCMS Content Management System

The #dotcache directive allows you to wrap arbitrary blocks of html and velocity code in a time limited static cache. It can be used many times in a single page, template, container or content and can even be nested if needed. The #dotcache tag is intended to allow dotCMS web developers to fine tune the trade off of scalability vs. responsiveness in dotCMS pages and applications.

The key dotcache is also used to cache JSON responses from custom API calls; read more about this feature in the Scripting API documentation.

Finally, the #dotcache directive is distinct from its cousin, the newer $dotcache viewtool, which allows generic object-based caching. Together, these tools allow a wide range of caching behaviors for either rendered HTML or raw data.


Important

Page cache (Cache TTL property on the Page) always takes priority over Block cache. So, when using Block cache on a Page, you should set the Cache TTL value on the Page to 0, and use Block Cache to control the caching of the slow regions of your Page.


Usage:

#dotcache(String KEY,int TTL)
I am cached content or velocity
Timestamp: $date
#end
ParameterDescription
KEYA unique identifier string for the content to be cached.
TTLThe Time To Live (in seconds) for contents in the block.
After the TTL has expired, contents in the block should be retrieved directly from the content store without accessing the block cache.

Key Values

The value of the KEY string is entirely in hands of the web developer and can be dynamically set based on other velocity variables. Careful thought should be given as to what key to use, since the key will determine how the content is cached based on whether or not the key is shared between pages, sessions, and users.

Some variables you can use to build your keys include:

VariableDescriptionCache Sharing
${CONTAINER_INODE}Container inodeAll pages which share the container.
$!{user.userId}User IDAll content accessed by the same user.
$!{host.inode}Host inodeAll users accessing the same host.
$!{HTMLPAGE_INODE}Page inodeAll content displayed on the same page.
$!{URLMapContent.inode}URLMap Content inodeAll content for the same URL Mapped content item.
$request.getRemoteServer()Visitor's IP addressAll requests from the same visitor.
$request.getHeader("User-Agent")Visitor's User AgentAll visitors using the (exact) same user agent (browser, device, etc.).
$request.getHeader("Accept-Language")Visitor's browser languageAll visitors who have selected the same browser language.
$session.getAttribute("com.dotmarketing.htmlpage.language")The visitor's selected languageAll visitors who have selected the same language in dotCMS.

TTL and Cache Invalidation

The TTL (Time To Live) parameter is an Integer, specified in seconds. dotCMS adds the TTL to the creation time of the cache entry and compares:

  • If the creation time + TTL >= now() the content will be returned from the block cache.
  • Otherwise the cache entry will be invalidated and the content will be fetched directly from the dotCMS content store.

TTL Example

  • 10:00 You hit a page with the following dotcache directive:
    #dotcache("mykey", 3600)
    
    • Result: First hit, cache miss.
      • The content is added to the cache with a create time of 10:00.
  • 10:30 You hit the page again:
    #dotcache("mykey", 3600)
    
    • Result: Cache hit.
      • The content is returned from the cache, because it is still within 3600 seconds of the create time of 10:00.
  • 10:35 You hit the page again, this time with the same key and a different TTL:
    #dotcache("mykey", 1800)
    
    • Result: Cache miss.
      • Content in the cache has a create time more than 1800 seconds ago. Cache entry is invalidated and content is re-fetched from the dotCMS content store and placed in the cache with a create time of 10:35.

URL Parameters

The block cache directive may also take two parameters via URL which change how the #dotcache regions on the page are handled:

URL ParameterDescription
?dotcache=noForce “no caching” of all content in #dotcache blocks on the page.
e.g., force a cache miss on all #dotcache blocks (but do not reset the TTL).
?dotcache=refreshForce all #dotcache entries to be invalidated;
This both forces a cache miss on all entries and resets the TTL for all cache entries.

These directives can be executed via any web browser on any page, by adding them to the end of the URL, e.g.

http://mysite.com/home/index.dot?dotcache=no&dotcache=refresh

Invalidating the Cache

The #dotcacheinvalidate directive allows you to programatically invalidate any item in the cache from velocity. This can be helpful when applications update pages or content and need the cache refreshed immediately.

Usage:

#dotcacheinvalidate(String key)

For a complete purge of the entire cache associated with the #dotcache directive, and the Dotcache Viewtool, visit the Maintenance panel under System → Maintenance and navigate to the Cache tab. There, use the dropdown menu to select the Block Directive cache, and then click the Flush button.

Nesting Caches

Caches may be nested within one another, but the resultant behavior is worth noting: Specifically, a child block can never have a shorter effective TTL than a parent block, and will not refresh until both blocks' TTLs have expired.

Take the following code:

#dotcache( "abc", 10)
    $date
    #dotcache( "xyz", 15 )
        $date
    #end
#end

In the above example, the outer cache is set to auto-invalidate every 10 seconds, and the inner every 15 seconds. As such, there will be various points at which they will either both yield a miss, both yield a hit, or one will hit while the other misses, in either configuration.

However, in the event that the inner block is a cache miss while the outer is a cache hit, then the user will still not activate the inner block, instead returning the outer block as rendered and cached, without performing any Velocity operations therein.

So, viewing it incrementally, refreshing every 5 seconds after the first visit:

RefreshParent Cache TimeChild Cache Time
0s12:00:00 PM12:00:00 PM
5s12:00:00 PM12:00:00 PM
10s12:00:10 PM12:00:00 PM
15s12:00:10 PM12:00:00 PM
20s12:00:20 PM12:00:20 PM
25s12:00:20 PM12:00:20 PM
30s12:00:30 PM12:00:20 PM
35s12:00:30 PM12:00:20 PM
40s12:00:40 PM12:00:40 PM

As you can see, the child cache block will not be read as a cache miss until registers as a miss for both the parent and child caches.

The same behavior obtains when the child block has a smaller TTL than the parent:

#dotcache( "abc", 10)
    $date
    #dotcache( "xyz", 3 )
        $date
    #end
#end
RefreshParent Cache TimeChild Cache Time
0s12:00:00 PM12:00:00 PM
5s12:00:00 PM12:00:00 PM
10s12:00:10 PM12:00:10 PM
15s12:00:10 PM12:00:10 PM
20s12:00:20 PM12:00:20 PM
25s12:00:20 PM12:00:20 PM
30s12:00:30 PM12:00:30 PM

As this makes clear, the child #dotcache can never refresh more often than its parent cache, but can be made to refresh less often.

#dotCache Examples:

Caching a remote JSON call, invalidating on failure

#set($cacheKey = "$!{host.inode}-remote-news")
## cache for 20m
#dotcache("$cacheKey", 7200) 
    #set($remoteNews = $json.fetch("https://api.remotenews.com/clientXXXXXX/news"))

    ## If remote JSON fetch fails, set a variable so we can invalidate
    #if($UtilMethods.isEmpty($remoteNews))
        #set($jsonFetchFailed = true)
    #else
        <ul class="vertical-list">
            #foreach($item in $remoteNews.data)
                <li> $item.releaseDate.dateUTC - <a href="$item.link.hostedUrl" class="story-title">$item.title</a></li>
                #if($velocityCount == 5) #break #end
            #end
        </ul>
    #end
#end

 ## If JSON fetch failed, invalidate and add entry for 30sec after which the remote call will be tried again
#if($jsonFetchFailed)
    #dotcacheinvalidate("$cacheKey")
    #dotcache("$cacheKey", 30)
        Remote news not available
    #end
#end

Using an empty cache as a simple IP rate limit

This works in tandem with the Request object to obtain a user's IP address, which is used as a cache key. Since the cache stores a rendered version of its contents and does not re-execute Velocity code on a cache hit (hence “empty”), $rateCheck will still evaluate to 0 if the user accesses the routine more than once every 5 seconds.

#set($rateCheck = 0)
#dotcache(${request.getRemoteAddr()}, 5)
    #set($rateCheck = 1)
#end

#if($rateCheck == 1) ## Success! It has been at least 5 seconds since the content was accessed.
    ## Expensive code goes here
#else ## Failure! It has been less than 5 seconds
    ## Inexpensive code goes here
#end

Notes

  • The #dotcache directive is decoupled from the dotCMS publishing architecture by design.
    • Unlike the other caches in dotCMS, it does not try to respect changes to site and content as they are made; a #dotcache block will serve the cached content until the TTL time has expired.
    • Keep your content contributors in mind (and in the loop) when deploying the #dotcache directive around their content.
  • A #dotcache block will always serve live content and data while browsing sites from the dotCMS backend administration tool's Edit/Preview/Live modes.
    • Therefore testing of the block cache should always be done from the front end of a dotCMS web site.
    • In addition, Admin mode will always show you a dynamic view of the content, so you should always test caching while logged in as a non-Admin user.
  • Important: The system will take a few minutes to pick up a new #dotcache directive and store the new key.
    • So you may wish to wait a few minutes to test a new block cache after you have applied it to a certain portion of a page.

On this page

×

We Dig Feedback

Selected excerpt:

×