If you are working on a site that gets a decent amount of traffic and are wondering how to speed up performance there are a number of things to try. One of the most beneficial is component caching (persisting CFCs).
When using the cfinvoke tag or the CreateObject function on your pages, it adds CPU time to process the requests to load all of those objects into memory. Under load this can make quite a difference in your page response times. One way to get around it is to put those objects into a shared scope so they only get instantiated once. This will save memory and CPU time on your server. Let’s get into it.
Application.cfm
<cfapplication name="yourApp" sessionmanagement="yes" sessiontimeout="#CreateTimeSpan(0,0,30,0)#">
<!--- Determine if we need to load the component manager cfc --->
<cfif not StructKeyExists(application,'componentManager') OR isDefined("URL.init")>
<cfset loadCom = true>
</cfif>
<cfif loadCom>
<!--- Use a named lock for shared scopes, does not lock the entire scope, just this block of code --->
<cflock name="loadComponentManager" timeout="10" type="exclusive">
<!--- Create the componentManager object and run the init() function --->
<cfset application.componentManager = CreateObject("component","path.to.componentManager").init()>
</cflock>
</cfif>
<!--- Most of my UDF’s are already available in the request scope, you can create a pointer to application.componentManager if easier --->
<cfset request.componentManager = application.componentManager>
/path/to/componentManager.cfc
<cfcomponent displayname="Component Manager" hint="Handles caching and distribution of components.">
<!--- The init function creates the structure that will hold instances of our components --->
<cffunction name="init">
<cfset cachedComponents = structNew()>
<cfreturn this>
</cffunction>
<cffunction name="getComponent">
<cfargument name="component" required="yes" type="string">
<cfset var path = "path.to.#arguments.component#">
<!--- Check to see if the component requested already exists in the structure, if it does not – create it and store it --->
<cfif not structKeyExists( cachedComponents, path )>
<cflock name="loadComponent" timeout="10" type="exclusive">
<cfif not structKeyExists( cachedComponents, path )>
<cfset tComponent = CreateObject("component",path)>
<!--- The structure key for this component is going to be the full path to it, while the structure value will be the component itself --->
<cfset tempVar = StructInsert(cachedComponents,path,tComponent,1)>
</cfif>
</cflock>
</cfif>
<!--- Return the cached copy of the component back to the user --->
<cfreturn cachedComponents[path]>
</cffunction>
</cfcomponent>
Referencing the cached components
You can reference your cached components like so:
<cfscript>
// The getComponent function takes only 1 argument, the name of the CFC you are trying // to call.
// separated
myObj = request.componentManager.getComponent(‘testCFC’);
myQuery = myObj.getMyQuery();
// All in 1 line
myQuery = request.componentManager.getComponent(‘testCFC’).getMyQuery();
</cfscript>
IMPORTANT NOTES
There are a few very important things to remember when using this technique. Once implemented you will have changed the nature of your CFC’s, no longer do you create a whole new instance for each user, all users now share the same instance of the component. The componentManager.cfc is responsible for dishing out the cached versions. This means no instance data in any cached CFC’s. Anything written to the ‘variables’ scope (or ‘this’ scope) inside your CFCs effectively becomes the same as an application variable by nature (and would need to be locked to be used properly). This means to avoid race conditions, do not keep instance data for any cached cfc. In fact, I find it best to var ALL local variables in my cached functions like so:
<cffunction name="someTestFunction" access="public" returntype="query" displayname="My Name" hint="Describe Me">
<cfargument name="userID" required="yes">
<!--- setting these variables first is important so you don’t get any other users’ data when you run the function, notice I even var the query names --->
<cfset var qGetUsers = ‘’>
<cfset var tDate = ‘’>
<cfset tDate = now()>
<cfquery datasource="queryDSN" name="qGetUsers">
SELECT * FROM USERS WHERE hireDate < #tDate# and userID = #arguments.userID#
</cfquery>
<cfreturn qGetUsers>
</cffunction>
Sometimes it can be a bit tricky to diagnose when you actually do have concurrency issues (race conditions), so it’s best to follow a few coding guidelines to make sure your functions are safe for persisting.