DevLearningTools

MODULE 16 · LESSON 01

Caching

The three layers of caching in ColdFusion: query caching with cachedwithin/cachedafter, object caching with cachePut()/cacheGet()/cacheRemove(), and page-level caching with cfcache, plus a real naming difference between Adobe ColdFusion and Lucee.

New lessons are added one at a time as the course gets built out — a graded quiz for each lesson is still on the way.

Module 15 was about not doing the wrong thing. Module 16 starts with doing the right thing faster: the cheapest performance win in any application is skipping work you've already done once. ColdFusion gives you three different layers to do that at, query results, arbitrary objects, and whole rendered pages, and each one fits a different situation.

Learning Objectives

After completing this lesson, you'll be able to:

  • Cache a query's results with cachedwithin and cachedafter.
  • Cache an arbitrary value with cachePut(), cacheGet(), and cacheRemove().
  • Cache a whole page or page fragment with cfcache.
  • Use cacheRegionNew(), cacheIdExists(), cacheRemoveAll(), and cacheGetAllIds() to manage cache regions beyond basic put/get.
  • Know that ORM has its own separate caching layer, distinct from cfcache and cachePut().
  • Know a real naming difference between Adobe ColdFusion and Lucee for the same caching feature.

How Caching Fits Together

Request Comes In

same query, object, or page as before

Check the Cache First

cachedwithin, cacheGet(), cfcache

Miss? Do the Real Work

run the query, build the page

Store It, Return the Result

next request gets the fast path

Four Layers of Caching

LayerToolCaches
Query cachingcachedwithin / cachedafter on cfqueryA single query's result set
Object cachingcachePut() / cacheGet() / cacheRemove()Any value: a struct, a computed result, an API response
Page cachingcfcacheAn entire page or page fragment's rendered output
ORM cachingORMExecuteQuery(cacheable=true), ORMEvictEntity()Entities and queries loaded through ColdFusion's ORM

Query Caching

cachedwithin and cachedafter both reuse a previous result instead of re-running the query, they just measure differently. A cached result is only reused if the SQL statement, datasource, query name, and credentials all match exactly, change any of those and it's a fresh query.

AttributeTypeMeaning
cachedwithinTimespan (createTimeSpan())Reuse the result if it was run within this duration from now
cachedafterDateReuse the result if it was run after this specific date
Tag Syntax
<cfquery name="news" datasource="myDSN" cachedwithin="#createTimeSpan(0, 1, 0, 0)#">
    SELECT id, title FROM news
</cfquery>
NOTE

Query Caching also has to be enabled in the ColdFusion Administrator before either attribute takes effect.

Object Caching

cachePut()/cacheGet()/cacheRemove() cache anything, not just query results, a struct, an expensive computed value, a parsed API response.

CFScript
// store, with a 30-minute expiry
cachePut("homepage_stats", statsStruct, createTimeSpan(0, 0, 30, 0));

// retrieve, checking for a miss
cachedStats = cacheGet("homepage_stats");
if (isNull(cachedStats)) {
    cachedStats = buildStatsStruct();
    cachePut("homepage_stats", cachedStats, createTimeSpan(0, 0, 30, 0));
}

// remove early, e.g. after the underlying data changes
cacheRemove("homepage_stats");
NOTE

cacheGet() returns null on a miss, isNull() is the standard way to check for that before using the result.

More Cache Functions Worth Knowing

cachePut()/cacheGet()/cacheRemove() cover the common case, but the full cache-functions reference has more, including the one that actually creates a named region in the first place.

FunctionDoes
cacheRegionNew(region [, properties, throwOnError])Creates a named cache region. Adobe's own example calls this before the first cachePut() into that region, rather than relying on cachePut() to create it implicitly
cacheIdExists(id [, region])Returns true/false for whether a key exists, cleaner than cacheGet() plus isNull() when you only need to check
cacheRemoveAll([region])Clears every object in a region (or the default region) at once
cacheGetAllIds([filter, cacheName, isAccurate])Lists every key currently cached, useful for debugging or building admin tooling

ORM Has Its Own Separate Caching Layer

If an application loads data through ColdFusion's ORM (covered back in Module 8), that's a fourth caching layer entirely, separate from cfcache and cachePut(). ORM caching works at two levels: a session-level cache that holds entities only for the current ORM session (EntityReload() forces a fresh database hit), and a secondary-level cache that persists across sessions, the one actually worth configuring deliberately.

CFScript
// cache this query's results at the secondary level
availableArts = ORMExecuteQuery(
    "from CArt where issold = 0",
    {},
    false,
    { cacheable: true, cachename: "availableArtsQuery" }
);

// evict explicitly once the underlying data changes
ORMEvictEntity("CArt");
ORMEvictQueries("availableArtsQuery");
NOTE

ORM caching defaults to Ehcache too, but it's pluggable, Adobe's docs also list JBossCache, OSCache, SwarmCache, and Tangosol Coherence Cache as supported secondary cache providers.

A Real Detail: Same Feature, Different Parameter Name

Both engines support named cache regions, separate pools so unrelated cached data doesn't collide or get flushed together, but the parameter is named differently. Adobe ColdFusion's cachePut() calls it region, Lucee's calls the equivalent parameter cacheName (with region accepted as an alias). Writing cross-engine code that uses named regions is one more place worth double-checking against the target engine's own docs.

Page-Level Caching with cfcache

cfcache caches rendered output instead of data, an entire page, or just the content between its tags.

actionDoes
cache (or optimal)Server-side and client-side caching together
serverCacheServer-side only
clientCacheBrowser-side only
flushClears cached pages, optionally scoped with expireURL's wildcard pattern
get / putReads or writes the object cache directly, the same cache cachePut()/cacheGet() use
Tag Syntax
<cfcache action="cache" timespan="#createTimeSpan(1, 0, 0, 0)#" idletime="#createTimeSpan(0, 12, 0, 0)#">
    <div id="daily-deals"><!--- expensive content to render --></div>
</cfcache>
NOTE

Adobe ColdFusion's caching (query, object, and cfcache alike) runs on Ehcache under the hood, configured through ehcache.xml in the ColdFusion root's lib directory. Lucee also supports a content action on cfcache, caching only the tag's body rather than a complete template.

Common Beginner Mistakes

Assuming cachedwithin keeps working after the query's SQL changes slightly

The cache key depends on the exact SQL statement matching. Even a whitespace-only change to the query text means the next call is a cache miss, not reused.

Forgetting to enable Query Caching in the ColdFusion Administrator

cachedwithin/cachedafter on a cfquery silently do nothing (always running fresh) if that setting isn't enabled server-wide.

Not checking isNull() after cacheGet()

A cache miss returns null, not an error. Code that uses the result directly without checking fails or behaves incorrectly on a miss instead of falling back to computing the value.

Assuming cachePut() with a new region name creates that region automatically

Adobe's own documented pattern calls cacheRegionNew() explicitly before the first cachePut() into a new region. Relying on implicit creation isn't the documented behavior.

Not realizing ORM-loaded entities use a completely separate cache from cachePut()

Clearing the object cache with cacheRemove() or cacheRemoveAll() has no effect on ORM's own session-level or secondary-level cache. That needs EntityReload() or ORMEvictEntity()/ORMEvictQueries() instead.

Caching a page with cfcache that includes user-specific content

Page-level caching serves the same cached output to every visitor during the cache window. Content that differs per logged-in user shouldn't be wrapped in cfcache without accounting for that.

Best Practices

  • Match the caching layer to the problem: query results with cachedwithin, arbitrary values with cachePut(), whole rendered output with cfcache.
  • Always check isNull() on a cacheGet() result before using it.
  • Use named cache regions to keep unrelated cached data from being flushed together, checking the correct parameter name (region vs cacheName) for the target engine.
  • Call cacheRemove() explicitly when the underlying data changes, rather than waiting out a long timespan.

Interview Questions

What's the difference between cachedwithin and cachedafter on a cfquery?

cachedwithin is a relative timespan measured back from now, cachedafter is an absolute date. Both reuse a cached result only if the SQL, datasource, query name, and credentials all match exactly.

What does cacheGet() return on a cache miss, and how should that be handled?

It returns null. The standard pattern is checking isNull() on the result, and if true, computing the value and storing it with cachePut() for next time.

Why might a cross-engine ColdFusion/Lucee codebase need to pass a different parameter name into cachePut() for named cache regions?

Adobe ColdFusion names that parameter region, Lucee names it cacheName (though it accepts region as an alias). The feature is equivalent, the parameter name isn't guaranteed to be.

Summary

In this lesson, you cached a query's result with cachedwithin/cachedafter, cached an arbitrary value with cachePut()/cacheGet()/cacheRemove(), managed cache regions with cacheRegionNew()/cacheIdExists()/cacheRemoveAll(), cached rendered page output with cfcache, saw that ORM has its own separate caching layer entirely, and saw a real naming difference between Adobe ColdFusion and Lucee for named cache regions.

What's Next?

The next lesson covers broader performance tips: where else to look once caching alone isn't enough.