views:

64

answers:

3

We have a data driven ASP.NET website which has been written using the standard pattern for data caching (adapted here from MSDN):

public DataTable GetData()
{
   string key = "DataTable";
   object item = Cache[key] as DataTable;
   if((item == null)
   {
      item = GetDataFromSQL();
      Cache.Insert(key, item, null, DateTime.Now.AddSeconds(300), TimeSpan.Zero;
   }
   return (DataTable)item;
}

The trouble with this is that the call to GetDataFromSQL() is expensive and the use of the site is fairly high. So every five minutes, when the cache drops, the site becomes very 'sticky' while a lot of requests are waiting for the new data to be retrieved.

What we really want to happen is for the old data to remain current while new data is periodically reloaded in the background. (The fact that someone might therefore see data that is six minutes old isn't a big issue - the data isn't that time sensitive). This is something that I can write myself, but it would be useful to know if any alternative caching engines (I know names like Velocity, memcache) support this kind of scenario. Or am I missing some obvious trick with the standard ASP.NET data cache?

A: 

I can see that there's a potential solution to this using AppFabric (the cache formerly known as Velocity) in that it allows you to lock a cached item so it can be updated. While an item is locked, ordinary (non-locking) Get requests still work as normal and return the cache's current copy of the item.

Doing it this way would also allow you to separate out your GetDataFromSQL method to a different process, say a Windows Service, that runs every five minutes, which should alleviate your 'sticky' site.


Or...

Rather than just caching the data for five minutes at a time regardless, why not use a SqlCacheDependency object when you put the data into the cache, so that it'll only be refreshed when the data actually changes. That way you can cache the data for longer periods, so you get better performance, and you'll always be showing the up-to-date data.

(BTW, top tip for making your intention clearer when you're putting objects into the cache - the Cache has a NoSlidingExpiration (and a NoAbsoluteExpiration) constant available that's more readable than your Timespan.Zero)

PhilPursglove
Thanks for this, but I don't think that it helps. Locking an object for thread-safe update isn't much of a challenge. And I don't want to use SqlCacheDependency because I don't want to turn on Service Broker (plus its use is not appropriate for the particular application).
Yellowfog
A: 

First, put the date you actually need in a lean class (also known as POCO) instead of that DataTable hog.

Second, use cache and hash - so that when your time dependency expires you can spawn an async delegate to fetch new data but your old data is still safe in a separate hash table (not Dictionary - it's not safe for multi-reader single writer threading).

Depending on the kind of data and the time/budget to restructure SQL side you could potentially fetch only things that have LastWrite younger that your update window. you will need 2-step update (have to copy dats from the hash-kept opject into new object - stuff in hash is strictly read-only for any use or the hell will break loose).

Oh and SqlCacheDependency is notorious for being unreliable and can make your system break into mad updates.

ZXX
I'm sure that this is well meant, but I don't think that any of it actually addresses my question. I really don't need pointers in how to code round the problem (I was probably being over-ingenuous in my description in order to motivate the question). Really I'm interested in whether any data cache alternatives exemplify the pattern I describe, which seems to me an obvious kind of requirement.
Yellowfog
+2  A: 

You should be able to use the CacheItemUpdateCallback delegate which is the 6th parameter which is the 4th overload for Insert using ASP.NET Cache:

Cache.Insert(key, value, dependancy, absoluteExpiration,
    slidingExpiration, onUpdateCallback);

The following should work:

Cache.Insert(key, item, null, DateTime.Now.AddSeconds(300),
    Cache.NoSlidingExpiration, itemUpdateCallback);

private void itemUpdateCallback(string key, CacheItemUpdateReason reason,
    out object value, out CacheDependency dependency, out DateTime expiriation,
    out TimeSpan slidingExpiration)
{
    // do your SQL call here and store it in 'value'
    expiriation = DateTime.Now.AddSeconds(300);
    value = FunctionToGetYourData();
}

From MSDN:

When an object expires in the cache, ASP.NET calls the CacheItemUpdateCallback method with the key for the cache item and the reason you might want to update the item. The remaining parameters of this method are out parameters. You supply the new cached item and optional expiration and dependency values to use when refreshing the cached item.

The update callback is not called if the cached item is explicitly removed by using a call to Remove().

If you want the cached item to be removed from the cache, you must return null in the expensiveObject parameter. Otherwise, you return a reference to the new cached data by using the expensiveObject parameter. If you do not specify expiration or dependency values, the item will be removed from the cache only when memory is needed.

If the callback method throws an exception, ASP.NET suppresses the exception and removes the cached value.

I haven't tested this so you might have to tinker with it a bit but it should give you the basic idea of what your trying to accomplish.

Kelsey
+1. Definitely solves the OP's requirement for 'old data to remain current while new data is periodically reloaded in the background'. Great solution!
p.campbell
I'd always assumed that this call was made after the object had expired...who would ever have thought that reading the documentation would come in handy? Nice work.
Yellowfog