iFinity Blogs 

DotNetNuke Caching and a performance problem

Jan 30

Written by:
Friday, January 30, 2009 10:40 PM  RssIcon

Let me just preface this post with a declaration : I'm talking about caching objects in code, not module or page level caching for outputs.  Module level caching is where the contents of a module are cached so that DNN can speedily emit them back out instead of constructing the details from database records.  Page level caching is pretty much synonymous with the Snapsis Pageblaster module : which takes the contents of the entire page and caches them to reduce the amount of database calls.  A fine module and a staple for DNN sites everywhere : except mine, oddly, I've never taken the time to implement it.

No, what I wish to discuss here is the ability of the DotNetNuke framework to cache all sorts of objects and to handle all the messy plumbing for you.

A quick scan around documentation and let-me-google-that-for-you will show up an easy example like this :

DotNetNuke.Common.Utilities.DataCache.SetCache("myObject", myObject);

to store your object.  And then, somewhere else, this code to extract it again:

object myObject = DotNetNuke.Common.Utilities.GetCache("myObject")

if (myObject != null)

return (myType)myObject;

else

myObject  = new myType();

DotNetNuke.Common.Utilities.DataCache.SetCache("myObject", myObject);

return myObject;

So far so good : and this is what was being used in the Url Master module, for the purposes of storing the custom Urls and 301 redirects that the module stores.

But recently, on a couple of separate occasions, I had people report back to me that there were excessive database connections being opened, all using the 'ifty_GetTabsAndRedirects' stored procedure.  This procedure returns a union of the tabs in the system, along with any custom redirects that belong to those tabs.  One thing I've learnt in my all too long career of creating and solving bugs (otherwise known as programming) is that problems always have a reason, and sooner or later they'll show up again, usually at the most inconvenient time.  I had another person report a slow site, so I thought I had better look deeper into the problem.  In the past, I hadn't been able to reproduce the problem.

Looking through the code, it was doing everything that I expected; building up the object by calling the stored procedure, storing it in the cache, and moving on.  Or was it?  When I did a Sql Trace, I found to my horror this telling set of entries:

RPC:Completed    0    exec dbo.ifty_GetTabsAndRedirects @portalId=-1,@includeAdminPages=0,@includeHostPages=0   

RPC:Completed    0    exec dbo.ifty_GetTabsAndRedirects @portalId=-1,@includeAdminPages=0,@includeHostPages=0   

RPC:Completed    4    exec dbo.ifty_GetTabsAndRedirects @portalId=-1,@includeAdminPages=0,@includeHostPages=0   

RPC:Completed    5    exec dbo.ifty_GetTabsAndRedirects @portalId=-1,@includeAdminPages=0,@includeHostPages=0   

I checked around a couple of my test sites : it seemed to be DNN version independent, and the problem was the 2nd column : this numeric number is the 'duration' column in the Sql Trace.   It is increasing with each call : a performance error which is log(n) - not good.  The reason is presumably because each subsequent call has to wait for the previous read on the tables to finish.

Puzzled, I delved further and put a couple of trace statements into the code to follow what was happening.  And I got quite a shock, because a trace statement right after the GetCache() call return a null object.  Because of this, the stored procedure was called - repeatedly as each time the object was stored in the cache, and then promptly disappeared.  Note that this is in the Friendly Url generation side of the module : the holdup comes when working out which urls to use for things like the menu and other page links.  The bigger the number of tabs, the worse the problem.

Now this didn't look like some sort of resource problem (the webserver will flush the cache when the memory allocation reaches a pre-set point) because there are two cached objects in the Url Master module : one for the rewrite dictionary, and one for the custom urls and redirects.  The other, cached object (the rewrite dictionary) was being cached just fine, despite being 4x the memory footprint.

Solving the Caching Problem

Here's where I'd love to share the brilliant debugging and problem solving skills I used to fix the problem.  Only I can't, because I could never work out what was wrong.  To me, it would seem that using the simple overload [setCache(key,object)] just doesn't work properly. 

What I did in the end was to use the same code I used for the Url Dictionary cache.  This was a different overload because it used a callback and a fixed expiration time.

Here's the code:

DateTime absoluteExpiration = DateTime.Now.Add(settings.CacheTime);
DataCache.SetCache(UrlDictKey, urlDict, null, absoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.AboveNormal, onRemove, settings.CachePersistRestart);

This code uses a different overload, and for some reason, works properly.  Testing confirmed it : previously, when hammering the test server with requests, eventually it would choke up as the database loaded up on queries to the same table over and over again.   Now, there would be one call to the sp, the item would be cached, and the requests could go through without choking.

So why the blog post?  Well, I'm hoping someone will be able to explain to me what I might have missed in my debugging.

The End Result

Because of all this : the Url Master version 1.13.02 will have the shortest release time of any release yet : it's now being replaced by the 1.13.03 module after only 1 day in circulation.  I would recommend anyone running the Url Master module to upgrade to 1.13.03 (or better, once this post ages and new releases are made) : I don't know exactly when this problem first appeared, but I would wager that it goes back to the 1.11 versions.  The 1.13.03 release contains the changes to the caching code, which in turn behaves as it should : quickly.

The new release is available here : iFinity Url Master Products Page

Tags:
Categories:
Location: Blogs Parent Separator Crafty Code

7 comment(s) so far...


Gravatar

Re: DotNetNuke Caching and a performance problem

Bruce - I am singing of joy, a great module and now everything works with a blasting performance. Now my DNN site is running DNN 4.9.1 + Snapsis PageBlaster + iFinity URL Master = Great SEO at high performance. Keep up the good work. Rgds/Gunnar

By Gunnar Snellman on   Saturday, January 31, 2009 3:25 AM

Re: DotNetNuke Caching and a performance problem

Hi Bruce,
I think maybe it is related to multithreading problem and a lock would prevent entering the cache subsequent times?
J.

By StoreIntegrator on   Friday, April 03, 2009 6:14 AM
Gravatar

Re: DotNetNuke Caching and a performance problem

Hi Bruce,
Did the problem also affect the Friendly Url Provider and did you fix it in that too?

By Laurence on   Friday, June 26, 2009 5:53 AM
Gravatar

Re: DotNetNuke Caching and a performance problem

@Laurence it shouldn't be affecting the Friendly Url Provider because it works in a slightly different way w/regards to generating the Friendly Urls. However, I'll double check to make sure the same problem doesn't exist.

By Bruce Chapman on   Friday, June 26, 2009 10:12 AM
Gravatar

Re: DotNetNuke Caching and a performance problem

Hi, did you find out about the Friendly Url Provider ?

By Laurence on   Saturday, July 04, 2009 2:55 AM
Gravatar

Re: DotNetNuke Caching and a performance problem

The problem we see in the "friendly url provider" is that it accesses a portal dictionary or tab dictionary which results in "getAllTabs". That effectively pulls in the entire database of tabs/tabpermissins/modulepermissions. Nice on a small dataset, killer on a production site that has thousands of portals. I had do rewrite the cache code to never call getalltabs and am using the database as a database.

By Scott Kuhn on   Wednesday, July 08, 2009 2:32 AM
Gravatar

Re: DotNetNuke Caching and a performance problem

@scott It's true that the Friendly Url Provider is using this stored procedure. As it doesn't have an installer I am reluctant to create a more specific procedure, because then the install process would be more difficult. People with larger websites always have the option of writing their own proc and changing the code, as you have done.

By Bruce Chapman on   Wednesday, July 08, 2009 9:15 AM

Your name:
Gravatar Preview
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Add Comment   Cancel 
Bruce Chapman
Hi, I'm Bruce Chapman, and this is my blog. You'll find lots of information here - my thoughts about business and the internet, technical information, things I'm working on and the odd strange post or two.

 

Share this
Get more!
Subscribe to the Mailing List
Email Address:
First Name:
Last Name:
You will be sent a confirmation upon subscription

 

Follow me on Twitter
Stack Exchange
profile for Bruce Chapman at Stack Overflow, Q&A for professional and enthusiast programmers
Klout Profile