Posted in ColdFusion | Posted on 06-22-2011 | 3,039 views
About a week or so ago Dave Ferguson (who has been leading up development of the mobile version of BlogCFC did some work to the "version service" used by BlogCFC installs. Whenever you first enter the administrator of BlogCFC the code does a http ping to a central service to check and see if a new version is available. (That by itself is not the point of this blog entry so if folks want an explanation of that, just ask.) Previously the code was static and I had to update it as part of my release cycle. Dave did some nice work to make it completely automated. But afterwards, I began to get an error from it.
I couldn't understand why. His code was simple. Check the cache (using cacheGet) and if it was empty, load up some data from RIAForge. Here is a snippet to give you an idea:
2<cfif isNull(versionInfo) or structKeyExists(url, "reinit")>
3 <cftry>
4 <cfhttp url="http://www.riaforge.org/index.cfm?event=xml.userprojects&uid=15" timeout="5">
The data in the cache was a structure with 3 keys. The error I received was just for one key that mysteriously did not exist. If I forget a refresh on the cache (you can see there the URL hook to do so), it immediately went away. Then - sometime later - the error would return.
Then it occurred to me. What if versionInfo as a cache name was being used elsewhere? I did a quick search of the code base for blogcfc.com (the marketing site, not the blog engine) and right away discovered that not only was there another bit of code using the same cache name, it was using similar code as well. It's why I ended up with a struct that had 2 of the same key names.
*sigh*
So how would you avoid this? At my current job, I like to call some times of code "red flag" issues. Basically, if you do X, you need to let the rest of the team know. That involves things like adding or updating JavaScript libraries, modifying Application.cfc, or even adding new Session variables. In a large application, if you use session.name to represent something, are you sure some other part isn't using "name" as well in the Session scope? In this particular case, a more precise naming scheme could have helped. I ended up changing it to versionSystemInfo, but I could have gone with "remoteSystemUpdateInfo". Yeah that's a bit long, but as you can see, I copy it right away into a local variable. This is one of those things where it may make sense to keep a simple text file in your project directory. Every time you make use of the cache, list the name and possibly the reason.
What about a runtime utility like cacheGetIds? That's helpful, but it's also possible you have a CFM that hasn't been run yet. There may be a 'branch' of your code that isn't run often (and therefore caches itself) that may be be missed by a call to cacheGetIds. (Although to be clear, I strongly recommend using the functions that inspect your cache. They can help monitor how large your cache is and - more importantly - how effective it is.)
Anyway - I hope this helps.


admin_foo
user_foo
comment_foo
entry_foo
...
The idea is to group the types of items you're caching so you don't have to remember whether a particular key was used throughout the program - just in the specific domain.
A second option is to use a different cache (CF 9.0.1 supports custom named cache regions in most of the tag functions as well as cfcache). There is minimal overhead with having multiple caches, although for something like a small blog, it's probably overkill.
I keep forgetting about regions. Too bad you have to edit an XML file first. I mean - not that that is a big deal, but it would be cool to be able to define the region in onApplicationStart.
Always returns names in uppercase, which is ugly to read. Is there some kind of getMeta() or something?
cachePut('foo', 'bar', 'myCustomCache')
cacheGett('foo', 'myCustomCache')
@Patrick, cacheGetAllIDs() is evil. It's fine if you only have a fed hundred (or thousand) items in teh cache, but if you have a cache with hundreds of thousands or millions of cache entries, you don't want to call that function.
"Edit ehCache.xml (cfroot/lib)to set the properties for user-defined caches as shown in the following example"
I read it as required. So are you saying that if I use cache region FOO and don't define it, it will use some default set of properties? And if I want to tweak it, THEN I go to XML? (If so, I'd still argue that it would be cool to define the props via CFML.)
1. Hard code the congig in ehcache.xml
2. Use my cacheNew() UDF to create the custom cache with customizable properties. I keep forgetting to release it. I'll do a quick blog post when I get to work, then send it over to cflib as well.
To answer your 2nd question, the answer is it depends ;-) In the case of blogCFC, that's definitely an option. You could first do a check to see if the cache exists, and if not, create it. It all depends on your use case and how you want to config it. If you're happy with the values set by default, then you wouldn't need to create a new custom cache on application start - you could just reference your custom cache name when you do your gets/puts. On the other hand, if you do want to tweak the cache config, you can go the hard coded xml route, which is fine if you have control over your server. However, since blogCFC is really a packaged app, I think it should be as self-contained as possible and shouldn't rely on having to make changes to the xml congig file. So in that case, perhaps having the check/initialization on app start makes the most sense.
<cf_soapbox>
"The primary reason is that programmers in the late 1960?s & 70?s for C and Unix decided to optimize the compilers and parsers. At the time computers were much slower and it was faster to compare identical strings rather then normalizing the upper/lower case of the strings. Back in 1969 when there were was no such thing as personal computers this optimization made a lot of sense." [Greg Raiz, Raizlabs]
setCacheValue(chacheKey,cacheValue,getCurrentTemplatePath())
Then inside the service every time that i set a value I log the key, the time it was set, and the .cfm or .cfc that set the key. It makes debugging issues much easier to just dump the log.
Anyway, that just a technique that has worked will for me.
[Add Comment] [Subscribe to Comments]