Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at the jQuery Conference 2010 (Boston, MA) with:

Factoring Out Key-Creation For Use With ColdFusion 9 Caching Methods

By Ben Nadel on
Tags: ColdFusion

Last night, I finished going through Aaron West's 14-Day series on ColdFusion 9 caching. It was a well organized, easy to understand set of tutorials that covered both the template and object caching now available (and improved) in ColdFusion 9. Inspired by what he wrote, I thought I'd share an approach to caching that I experimented with when coding my Best of CF9 entry.

While the ColdFusion object cache is application-specific, there is ample opportunity within a given application to have key collisions. As such, we will most likely need to cache objects using more than just, say, their ID values since multiple objects within an application are likely to have the same ID. To prevent these key collisions, I chose to prefix my object IDs with context-specific information. So, for example, if I had an object with ID "3," that was being cached by a "Service," I would cache it at the key, "service.3".

This definitely helps with key collisions, but it now requires business logic to be baked into cache key creation. To keep this as simple as possible, I factored out the construction of cache-keys into their own function such that the calling code only needed to know the primary caching key and not the contextual key. To demonstrate this, I have created a "Girl Proxy" object that acts as proxy / service between the calling code and ColdFusion 9's object cache. You will notice that as it puts and gets Girl objects to and from the cache, it only requires IDs, which it then, internally transforms into true cache keys.

GirlProxy.cfc

  • <cfcomponent
  • output="false"
  • hint="I provide an access and creation facade for girls.">
  •  
  •  
  • <cffunction
  • name="init"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I initialize this object.">
  •  
  • <!---
  • Create a prefix to be used when storing girls in the
  • cache. This will be helpful when we need to gather
  • information about the entire cache as it related to
  • this service object.
  • --->
  • <cfset this.cachePrefix = "girlProxy.cfc." />
  •  
  • <!---
  • I am the internal ID (counter) of the girls that have
  • been created.
  • --->
  • <cfset this.autoIncrementer = 0 />
  •  
  • <!--- Return this object reference. --->
  • <cfreturn this />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="clearCache"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="I clear all the cached girls.">
  •  
  • <!--- Loop over all the cached keys for this obejct. --->
  • <cfloop
  • index="local.cacheKey"
  • array="#this.getCacheIDs()#">
  •  
  • <!--- Delete the cached girl. --->
  • <cfset cacheRemove( local.cacheKey ) />
  •  
  • </cfloop>
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="deleteGirl"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="I remove the given girl from the cache.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="girl"
  • type="struct"
  • required="true"
  • hint="I am the girl being deleted."
  • />
  •  
  • <!---
  • Remove the girl from the cache. Notice that we are
  • passing the ID to the getCacheKey() method first so
  • we don't have to spread the knowledge of how the key
  • is made.
  • --->
  • <cfset cacheRemove(
  • this.getCacheKey( arguments.girl.id )
  • ) />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="getCacheKey"
  • access="public"
  • returntype="string"
  • output="false"
  • hint="I get the cache key used to get / set a girl cache item with the given ID.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="id"
  • type="string"
  • required="true"
  • hint="I am the unique ID of the girl."
  • />
  •  
  • <!---
  • Create the cache key based on the cache prefix and
  • the given ID.
  • --->
  • <cfreturn (this.cachePrefix & arguments.id) />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="getCacheIDs"
  • access="public"
  • returntype="array"
  • output="false"
  • hint="I return an array of IDs that are related to the cache being used by this object.">
  •  
  • <!--- Create our result array. --->
  • <cfset local.ids = [] />
  •  
  • <!---
  • Loop over all the IDs in the cache.
  • NOTE: this will contains more than just IDs specific
  • to this girls proxy.
  • --->
  • <cfloop
  • index="local.cacheKey"
  • array="#cacheGetAllIDs()#">
  •  
  • <!---
  • Check to see if this ID starts with the girl-
  • prefix. If it does, then it's an item in our
  • girl cache.
  • --->
  • <cfif (left( local.cacheKey, len( this.cachePrefix ) ) eq this.cachePrefix)>
  •  
  • <!--- Add key to the results array. --->
  • <cfset arrayAppend( local.ids, local.cacheKey ) />
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  • <!---
  • Return the array of cache IDs that are specific to
  • our proxy object.
  • --->
  • <cfreturn local.ids />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="getCacheTimeout"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I return the time span used to set the cache timeout.">
  •  
  • <!--- Return 10 minutes. --->
  • <cfreturn createTimeSpan( 0, 0, 10, 0 ) />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="getGirl"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I return the girl cached at the given ID.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="id"
  • type="string"
  • required="true"
  • hint="I am the unique ID of the girl."
  • />
  •  
  • <!---
  • Return the cached girl. Notice that we are passing the
  • ID to the getCacheKey() method first so that we don't
  • have to spread the knowledge of how the key is made.
  • --->
  • <cfreturn cacheGet( this.getCacheKey( arguments.id ) ) />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="getGirls"
  • access="public"
  • returntype="array"
  • output="false"
  • hint="I get all the girls cached by this service.">
  •  
  • <!--- Create a results array. --->
  • <cfset local.girls = [] />
  •  
  • <!--- Loop over the known cache IDs. --->
  • <cfloop
  • index="local.cacheID"
  • array="#this.getCacheIDs()#">
  •  
  • <!--- Get the girl at the given ID from the cache. --->
  • <cfset local.girl = cacheGet( local.cacheID ) />
  •  
  • <!---
  • Since there may be race conditions, only add the
  • girl to the resultant array if the return girl is
  • not null.
  • --->
  • <cfif !isNull( local.girl )>
  •  
  • <!--- Append to results array. --->
  • <cfset arrayAppend( local.girls, local.girl ) />
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  • <!--- Return the cached girls array. --->
  • <cfreturn local.girls />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="newGirl"
  • access="public"
  • returntype="struct"
  • output="false"
  • hint="I return an empty girl struct.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="name"
  • type="string"
  • required="false"
  • default=""
  • hint="I am the default name of the new girl."
  • />
  •  
  • <!--- Return a new girl. --->
  • <cfreturn {
  • id = ++this.autoIncrementer,
  • name = arguments.name,
  • isHot = false
  • } />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="saveGirl"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="I save the given girl to the cache.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="girl"
  • type="struct"
  • required="true"
  • hint="I am the girl being cached."
  • />
  •  
  • <!---
  • Cache the girl. Notice that we are passing the ID
  • to the getCacheKey() method first so that we don't
  • hvae to spread the knowledge of how the key is made.
  • --->
  • <cfset cachePut(
  • this.getCacheKey( arguments.girl.id ),
  • arguments.girl,
  • this.getCacheTimeout()
  • ) />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  • </cfcomponent>

As you can see in the above code, all IDs are transformed into cache-keys through the method, getCacheKey(ID). This method takes the given ID and prepends the caching prefix. Not only does this prevent key collisions within the application, a contextual key prefix provides the added benefit that we can now retreive all keys directly related to a given context (ie. GirlProxy).

Here is a small demo of this code in action:

  • <!---
  • Create out girl proxy object (this will interact with the
  • cache so we don't have to).
  • --->
  • <cfset girlProxy = new GirlProxy() />
  •  
  •  
  • <!--- Create a new girl (Assumed ID: 1 ). --->
  • <cfset tricia = girlProxy.newGirl( "Tricia" ) />
  • <cfset tricia.isHot = true />
  •  
  • <!--- Create a new girl (Assumed ID: 2 ). --->
  • <cfset joanna = girlProxy.newGirl( "Joanna" ) />
  • <cfset joanna.isHot = true />
  •  
  • <!--- Save both girls. --->
  • <cfset girlProxy.saveGirl( tricia ) />
  • <cfset girlProxy.saveGirl( joanna ) />
  •  
  •  
  • <!--- ------------------------------------------------------ --->
  • <!--- ------------------------------------------------------ --->
  •  
  •  
  • <!--- Get Tricia (assumed her ID is "1" as she is the first). --->
  • <cfset tricia = girlProxy.getGirl( 1 ) />
  •  
  • Tricia (getGirl( 1 )):<br />
  •  
  • <!--- Output joanna. --->
  • <cfdump
  • var="#tricia#"
  • label="getGirl( 1 ) :: Tricia"
  • />
  •  
  •  
  • <!--- ------------------------------------------------------ --->
  • <!--- ------------------------------------------------------ --->
  •  
  •  
  • <br />
  • All Girls:<br />
  •  
  • <!--- Output all the girls. --->
  • <cfdump
  • var="#girlProxy.getGirls()#"
  • label="GetGirls()"
  • />
  •  
  • <br />
  •  
  •  
  • <!--- Delete Joanna. --->
  • <cfset girlProxy.deleteGirl( joanna ) />
  •  
  • All Girls (After Delete::Joanna):<br />
  •  
  • <!--- Output all the girls. --->
  • <cfdump
  • var="#girlProxy.getGirls()#"
  • label="GetGirls()"
  • />
  •  
  • <br />
  •  
  •  
  • <!--- Clear the cache of girls. --->
  • <cfset girlProxy.clearCache() />
  •  
  • All Girls (After clearCache()):<br />
  •  
  • <!--- Output all the girls. --->
  • <cfdump
  • var="#girlProxy.getGirls()#"
  • label="GetGirls()"
  • />

When we run the above code, we get the following page output:

 
 
 
 
 
 
Cache Key Creation Proxy For Use With ColdFusion 9 Caching Functionality. 
 
 
 

When you look at the above code, you might ask the question - Why don't I just create some sort of collection (ie. girls) and then cache that collection rather than caching each individual girl? The reason we cache individual girls and not a collection of girls is that we can leverage the implicit expiration management provided by the cache. If I were to create and manually maintain a collection, then I'd have to periodically flush old girls out of the collection; but, by caching girls individually, I can assume that ColdFusion will handle the flushing of old girls as needed. Of course, this logic only makes sense if you set your keys to expire (something I forgot to do in my BoCF9 entry!).

There's nothing revolutionary here - I certainly wouldn't say this is or is not a best practice; I just thought I'd share a caching approach that I was playing with.



Reader Comments

@Raymond,

I am not sure you would, at least not in this scenario. If you look at my getGirls() function, you will see that it checks for NULL values returned from the cache before it adds the girl to the resultant array; there is some assumed potential that the key set it gets might not be completely valid by the time it starts to act on it.

Reply to this Comment

I'm thinking more at the loop level for example:

cfloop
index="local.cacheID"
array="#this.getCacheIDs()#"

What if something manipulates the array after it begins looping?

Reply to this Comment

@Raymond,

It is possible that the array contains keys that are no longer valid; but since arrays are passed-by-value, once the proxy has a copy of the array, it will not be altered by any parallel thread.

Reply to this Comment

@Raymond,

Complex array items would be passed-by reference, but this is just an array of string values (cache IDs).

Reply to this Comment

@Raymond,

It's definitely possible - locking is not something that I think about too often. And, as long as there is no assertion that cacheGet() can't return a NULL value, I am not sure what the race condition could be.

Reply to this Comment

@Raymond,

The array="#this.getCacheIDs()#" you pointed out is thread-safe because getCacheIDs() is _NOT_ a property getter, but rather a method that returns a new array on each call.

@Ben,

I took a look at your code and the only race condition I could find was the ++this.autoIncrementer in the newGirl() method. On very rare occasions it could result in two different girls being assigned the same ID. That would be a real bee-otch of a bug to troubleshoot if it ever hit you.

Although I haven't tried it, I believe you could change autoIncrementer from a ColdFusion number to a java.util.concurrent.atomic.AtomicInteger object and replace ++this.autoIncrementer with this.autoIncrementer.incrementAndGet(). That ought to eliminate any race conditions around the generation of girl IDs.

Reply to this Comment

@Raymond

I think Dennis is right here. Once the array is created there wont be a possibility to change it while it loops.

@Dennis

Can you make an example when ++this.autoIncrementer creates two times the same ID?

Reply to this Comment

@Roman,

The critical section of this race condition is so small that it would be very difficult to demonstrate empirically. It would likely involve creating a very heavy load on the server and a large number of threads to guarantee a hit on the race condition during a test.

The above doesn't mean that the race condition should be ignored because the probability of the race condition occurring will grow the longer the code is used.

I found the following to be a nice short explanation of race conditions in Java:

http://www.cs.unm.edu/~markidis/lab13.html

"If two threads execute n=n+1 on a shared variable n at about the same time, their load and store instructions might interleave so that one thread overwrites the update of the other."

The above link includes a demonstration in Java code however it does not actually test n=n+1 because it is so difficult to reproduce such a race condition.

Let's instead look at ++this.autoIncrementer from the theory side. Although ++ is a single operator in ColdFusion, it is compiled to multiple Java bytecode instructions. The prefix form of ++ would go something like this:

1. Read the value from the variable address in main memory into a register.
2. Increment the value in the register.
3. Write the value from the register to the variable address in main memory.
4. Push the value in the register onto the stack as the expression value.

The ++ operator is not synchronized; this means that multiple threads can be running within the operator at the same time. If one thread executes step 1 while another thread is on step 2, both threads will end up with the same expression value.

If a two-thread race condition occurred in Ben's GirlProxy.newGirl() method with a starting this.autoIncrementer of 5, each thread would end up with its own girl structure but both girl structures would have an ID of 6.

Reply to this Comment

Okay, everything's clear, thanks for the detailed information.

Well, this doesn't sound like a big problem to me. I dont know much about CF/Java hacking, but this seams to be more a problem to DDOS than anything else.
What I'm not sure about is, why a multi-threading server would mix up two threads. I mean, two user accessing the ++ operator at the same time won't have the same thread, will they?

And another question to the current component. Call me dump, but isn't it possible to create simply two girls with the same ID, since the girls are cached for 10 mins and the this.autoIncrementer isn't? Or am I overlooking something?

Thanks for your info

Reply to this Comment

@Roman,

You're right in that in this case it's not a big problem: it's actually a very tiny problem. However a small problem can sometimes be worse than a big problem. For me, the chances are good that if I left such a race condition in my code, my application would work fine for a year before it hit a single person ... who would happen to be a senior executive of the company I work for. I would then be forced to spend a week researching the issue without finding any trace of a problem and have to resort to using quantum bogodynamics (http://www.catb.org/jargon/html/Q/quantum-bogodynamics.html) to explain what happened. Such things have already happened to me in the past.

Threads and concurrency are large topics and I won't try to explain it all here. The basic issue though is that when multiple threads (i.e. requests) make changes to the same object at the same time it can cause incorrect results. An object must be shared for it to be accessible by multiple threads; in CF that means linking the object to the session, application, or server scopes. So race conditions in CF only apply to objects kept in one of these scopes.

To make good use of a CFC such as Ben's GirlProxy, you'd most likely put it in the application scope. A simple way would be to assign it during onApplicationStart():

<cfset application.girlProxy = new GirlProxy() />

As long as the application scope doesn't time out and the server stays running, the application.girlProxy instance (along with the autoIncrementer value it contains) will remain in server memory. I believe the default application scope timeout is 1 day, and as long as your cache timeout is shorter than your application scope timeout you won't have to worry about girl ID's being reused in the way you described.

Reply to this Comment

I know what you mean with the senior executive ;)
Not every software acts the same, so I just guessed how the threading would work.
In my opinion the application server should create an instance for every request (a for user a, and b for user b) so there won't be any problems on the stack/heap side.
But since I have to learn a lot more about the basics of jrun and the coldfusion server (well, coding isn't everything) I welcome tips and hints from experts. So thanks for your information. I'm always pleased to learn more.

And yea, you're right, the application was the thing I missed. Nearly embarrassing, it looks like I've seen enough CF for today ;) But thanks for the second eyes. Now everything's clear.

Reply to this Comment

@Dennis,

To fix this, I suppose we could throw an exclusive, named lock around the code that executes the ++ pre-incrementor. That should solve the problem without causing too much of a bottle-neck.

Reply to this Comment

@Ryan,

I still cannot bring myself to use UUIDs. It's purely aesthetic - I just don't like the way they look. I'm a bit shallow that way ;)

Reply to this Comment

@Ryan,

Yeah, but I don't know what I'll say when my friends ask me, "Dude, what are you doing with that UUID?"... I give into peer pressure too easily.

Reply to this Comment

@Ben,

Yes, an exclusive named lock around the incrementor would prevent the proxy from assigning duplicate IDs. The advantage of a java.util.concurrent.atomic.AtomicInteger object is that it avoids the overhead of CF-level locking and is optimized to be as short as possible. It would be interesting to do perform an empirical performance test of cflock vs. AtomicInteger for this case.

I agree that UUIDs are unsightly, and I understand that when it comes to your Girl instances, looks are everything ;) I would not normally use them myself, however there are two benefits to UUIDs in this scenario that may be difficult to get any other way:

1. If you ever need to recreate the girlProxy instance, the new instance will not assign IDs that conflict with those generated by previous girlProxy instances. This would, for example, let you reset your application scope safely without having to discard all your previously cached objects.

2. If you ever need to have multiple girlProxy instances running concurrently (such as when deploying to multiple-instance or clustered environments), they are extremely unlikely to assign conflicting IDs. Without getting into too much mathematics, I believe you are more far likely to be struck by lightning in a year than to generate duplicate UUIDs during the same period of time.

Add to that the benefit @Ryan had already implied (no locking necessary), and you have a strong argument for using UUIDs in your proxy.

Reply to this Comment

@Dennis,

Yeah, the database transfer of records is always a big caveat. I've done enough partial-transfers in my life to know that creating then maintaining IDs across different auto-incrementers is a beast!

I'll look into the AtomicInteger; I've never seen that before. Thanks.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.