Just a quick tip for folks using ColdFusion 9's new caching system. Whenever you add something to the cache you must use a unique key. For a simple method, that's relatively easy:
return cachedData;
}
public any function doItSlow() {
var cachedData = cacheGet("slowProcess");
if(isNull(cachedData)) {
var cachedData = doSomethingSlow();
cachePut("slowProcess", cachedData);
}
That's a pretty trivial example, but you get the idea. A problem may arise, though, when your method takes arguments, especially multiple arguments. Imagine a typical "getX" type function that loads data. It may take arguments for the max number of items to return, the starting index, the order, and perhaps a search string. Imagine the following method signature:
function getStuff(numeric maxItems=10, numeric start=1, string sort="name asc", string search) {
Given that you have multiple arguments and therefore a large number of unique results, how could you cache the results? You could generate a key by examining the arguments scope:
var key = "";
key &= "#arguments.maxItems#_";
key &= "#arguments.start#_";
key &= "#arguments.sort#_";
if(structKeyExists(arguments.search)) key &="#arguments.search#";
This would create a key looking something like: 10_1_name_asc_foo. While this method works, it can get pretty complex. (Although if you start adding more arguments, you may have an overly complex API in the first place. That's a discussion for another day.) Here is a little trick I've taken to using that makes it even easier:
var key = "mymethod_#serializeJSON(arguments)#";
That's it. I use a prefix of "mymethod" so that when I examine the entire cache it is clear as to where the data belongs. The rest of the key is simply a serialized form of the arguments. It may be ugly, but it works perfectly well. Obviously you want to use with caution - especially since that search argument could be anything.
Archived Comments
It's worth noting caching a method's output as a function of its parameters will only work for deterministic methods. A non-deterministic method is not based solely on the arguments, but external sources. For instance, a method that accepted a date and returned a time span based on a dateDiff calculation using now().
I do like your idea of using serializeJSON. I generally have just concatenated and hashed the arguments (assuming they are simple values) but serializeJSON is a bit less code and I could still hash it just to cut down on the sheer size of the cache key.
Good points. Yeah, the hash would make it cleaner. Although in JSON form, I can still "read" it. I wouldn't expect a non-tech person to be able to read it, but then again, a non tech person wouldn't be examining the cache probably. Of couse, in JSON form, you could do some more deep reporting on the cache items, since you can deserialize it to examine how the method was used.
You know - ignore cache for a minute. It could be kinda useful to just log the serialized args in general. You would want to use ColdSpring though and an... aspect (may be wrong on the term there) so you aren't littering your model.
hmm.. would SerializeJSON() guarantee the order of arguments though? Argument scope is weird 'cause it behaves like an array and a struct at the same time. What happen if someone pass in an ArgumentCollection? Will there be a chance that the JSON string have the same arguments, but in different order?
The order will be consistent if the caller is consistent. In a typical MVC app that means everything goes to the service via the controller.
If not, then your results may vary. ;)
Can a hash method be used on a Coldfusion structure?
http://www.cfquickdocs.com/...
Input is a string.
That is why you would need to turn the struct into a string with a function such as serializeJSON.
Watch out for complex arguments like an object with references to your framework. That could get interesting.
As someone who is a super fan of hash()ing values to get KEYs, I think this approach is really cool. Way to think outside the box.
To be honest, I've been on a huge JSON kick lately. I also love it for logging too.
Well in the past our only real option for representing complex variables as a string was wddx. JSON just seems to much more lightweight and I think it's easier to eyeball values out of too.
Have you considered Java's hashCode() method?
See JavaDocs for java.util.HashMap as my comment was flagged as spam with the link in it.
You could potentially use this to store an identifier for the different combinations of arguments in the whole arguments structure, or make a small structure of just the arguments you care about. Under the hood, CF structures / scopes implement java.util.HashMap and I've used this before to compare de-dupe structures of data pulled from xml services for instance. It's also intended for this type of comparison, with the hash function being designed for wide variance in even slightly different input.
Ultimately though, a hash by definition can have collisions so I would only suggest doing this in cases where you expect relatively few combinations like a finite argument set. If the cache arguments can vary to a massive degree because of id's, search strings, etc. it isn't a great idea to cache them all anyway.
Though it is true that a hash can have collisions, the likelihood of that happening is very very very slim. Almost as slim as a GUID not being unique within the next 50 years.
Did you know every time you reply on Ray's blog his comment table burns a perfectly good GUID that will never get to be re-used. For goodness sake people, GUIDs aren't a renewable resource, we need to conserve them!
:)
Hahahahah - nice. :)
Interesting idea, I was thinking along the same lines... but then realized you can't use cache keys with commas in them since the caching functions take a comma separated list.
You could use something like
cacheKey = Replace(SerializeJSON(arguments),",","","all")
Sorry - what? cachePut/Get take an ID value that is just an id. So "a,b" as an ID should be legal.
Serialized JSON (as suggested above) will contain commas. You can't use commas in your cache keys. Take this code for example:
<cfset cacheKey = "test,or serialized JSON here">
<cfset cachePut(cacheKey,"foo") />
<cfset cacheRemove(cacheKey) />
#cacheGet(cacheKey)#
The cacheGet call will return "foo", even though I've tried to delete it on the line above.
Wow. I'd call that a bug then. Or at least it should throw an error. If I use key a,b and it stores it as A w/o warning, that's bad. Making a test case and will write a bug report.
I'm not able to reproduce this using this code:
<cfset cachePut("foo,moo", 1)>
<cfdump var="#cacheGetAllIds()#">
<cfset v = cacheGet("foo,moo")>
<cfoutput>#v#</cfoutput>
I'll try with yours.
Ok, to me, the bug is in cacheRemove. It fails to remove the key. So you can use commas for put/get, you just can't delete it. Can you confirm that?
Yes thats exactly what I was seeing. It gets placed into the cache, but it can't be removed. My guess is this is because cacheRemove() accepts a comma separated list of IDs to remove.
Even odder - if you use
cacheRemove(cachekey, true)
it throws an error, even though true is supposed to be the default.
Filing two bug reports.
One - inability to remove a key with commas when you are allowed to set it.
Two - if I do cacheRemove(.., true) it acts differently, which doesn't mesh with the docs that say exact=true is the default.