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
Parameter | Description |
---|---|
KEY | A unique identifier string for the content to be cached. |
TTL | The 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:
Variable | Description | Cache Sharing |
---|---|---|
${CONTAINER_INODE} | Container inode | All pages which share the container. |
$!{user.userId} | User ID | All content accessed by the same user. |
$!{host.inode} | Host inode | All users accessing the same host. |
$!{HTMLPAGE_INODE} | Page inode | All content displayed on the same page. |
$!{URLMapContent.inode} | URLMap Content inode | All content for the same URL Mapped content item. |
$request.getRemoteServer() | Visitor's IP address | All requests from the same visitor. |
$request.getHeader("User-Agent") | Visitor's User Agent | All visitors using the (exact) same user agent (browser, device, etc.). |
$request.getHeader("Accept-Language") | Visitor's browser language | All visitors who have selected the same browser language. |
$session.getAttribute("com.dotmarketing.htmlpage.language") | The visitor's selected language | All 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.
- Result: First hit, cache miss.
- 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.
- Result: Cache hit.
- 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.
- Result: Cache miss.
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 Parameter | Description |
---|---|
?dotcache=no | Force “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=refresh | Force 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:
Refresh | Parent Cache Time | Child Cache Time |
---|---|---|
0s | 12:00:00 PM | 12:00:00 PM |
5s | 12:00:00 PM | 12:00:00 PM |
10s | 12:00:10 PM | 12:00:00 PM |
15s | 12:00:10 PM | 12:00:00 PM |
20s | 12:00:20 PM | 12:00:20 PM |
25s | 12:00:20 PM | 12:00:20 PM |
30s | 12:00:30 PM | 12:00:20 PM |
35s | 12:00:30 PM | 12:00:20 PM |
40s | 12:00:40 PM | 12: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
Refresh | Parent Cache Time | Child Cache Time |
---|---|---|
0s | 12:00:00 PM | 12:00:00 PM |
5s | 12:00:00 PM | 12:00:00 PM |
10s | 12:00:10 PM | 12:00:10 PM |
15s | 12:00:10 PM | 12:00:10 PM |
20s | 12:00:20 PM | 12:00:20 PM |
25s | 12:00:20 PM | 12:00:20 PM |
30s | 12:00:30 PM | 12: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.
- Unlike the other caches in dotCMS, it does not try to respect changes to site and content as they are made; a
- 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.