Data Caching, SyncLock Applications, Expiry and HELP!


In way over my head.... I have a 2 web application that has some problems - most using cached datasets/tables. I have implemented data caching that loads some very frequently-used tables into cache. Essentially, the web app serves a number of domains and needs different settings/content for each - and much of that content is contained in the cached datatables/datasets but the datasets are not big but can be accessed 20-30 times in the life of a page request for a complex page.

The data caching was simply set up to hold the datasets/tables in cache and then expire them after 30 minutes - so I could reload any new settings/content. When in production under load the app had serious problems - I think related to race conditions when items in the cache expired - so I added created a LoadCache class and added a SyncLock on an object around the cache insert procedures to stop instances/threads trying to add stuff at the same time. But the problems remain it seems because multiple threads are trying to READ the cache object, then find the data doesn't exist (because it expired), then requests a load of the cache (and finds the lock on it) while another thread has already issued the request to load it. I am not sure this is what is actually happening as it really hard to trace/debug. In any event I suddenly get errors galore as threads somehow can't see the cached data.

Here is summary of what I've done:

class cacheloader
  private shared cachelock as new object

  public sub loadcache(tblname as string)
      '...get the table from db
        httpcontext.current.cache.insert(tbl) 'the table is actually in its own dataset
       end synclock
  end sub
end class

then elsewhere I have:

dim ds as new dataset
ds=ctype(httpcontext.current.cache("mytable"), dataset)
if isnothing(ds) then
      dim c as new cacheloader
      ds=ctype(httpcontext.current.cache("mytable"), dataset)
end if

I suspect that there is an issue with the scope of the cacheloader cachelock object (surely this is private to each instance of cahceloader?) and thus not a functioning lock? Should cacheloader be declared globally somewhere?

All that aside, I have come to the conclusion that the really-frequently accessed data needs to not expire in cache cuz I just can't seem to get it to work properly under load and keep everything synched. There is a good possibility that I haven't created and used the LoadCache class properly

Now to the question: if I load the datatable/sets into Application (i.e. as an Application("mydataset")) then when does this get refreshed? I have multiple worker processes configured - do each of these use the same Application object? If I load the dataset in Application during Application_Start, will each worker process run the Application_Start and have it's own copy of Application, or is it global/shared to all the worker processes. I ask this as I have the worker processes set in IIS to restart every 1/2 hour - and that would be an OK interval to reload this data.

Is there any worker process/instance specific way of loading this data so as each worker process stops/restarts, it gets its own (current) copy of data and loads it?

Sorry for the rambling...  What I thought was a good idea (using expiring cache for extremely frequently accessed data - sometimes 10+ times per page request) is not as easy as it would appear from MS docs. In fact, in no example of data caching I have seen from MS does they even mention the need to SyncLock to avoid multiple threads modifying the cache.

And... before anyone suggest sql data dependencies: I don't want the complexity of this. If I can't get simple expiring caches to work, I can't imagine having to try to debug sql data dependencies....

Thanks for any help.

Who is Participating?
RejojohnyConnect With a Mentor Commented:
application object does not have refresh or expires as cache objects ..

I also see that u have this code to add data into cache

i hope u know that cache has other parameters like priority, expiry, callback functions etc which can used for solving all ur problems .. pls read MSDN for all the features .. there is no need for locks etc .. if u feel that ur object in cache has to last for 30 minutes, u can set the priority to max and so it will never get removed till 30 minutes .. sliding expiration also helps for similar situation .. callback function can also be used so that when the item gets removed from cache, u can check the reason y it was removed and take approriate action ...

ctudorpriceAuthor Commented:
thanks - I ended up (glutton for punishment) totally rewriting the caching and am currently volume testing it. (I did have the other params for cache.insert).

I rewrote to use datatables in the cache instead of datatables in datasets because I thought that maybe datasets were adding a lot of overhead and were unnecessary. Also took your advice and moved the important tables to max priority. Put an Application.Lock AND a SyncLock around the cache insert - just to be double sure, and changed the scope of the object I was sync locking on to global - and generally simplified everything.

I'm still not clear on what the best approach to managing synchronization on the cache object is though - Application.Lock or SyncLock?

ctudorpriceAuthor Commented:
Just so I don't confuse anyone as much as I was confused....

There was/is no need for synclock or application.lock as the tables I was storing in cache are/were read-only - doh. SyncLock is only required to ensure that the same threads don't update read/write items in cache. I had a procedure that I called to load the table into cache and that did that but when the cache ran out of memory and cache memory was full, they were immediately removed. So, I changed that to a function that inserted the table into cache and returned the table it retrieved - rather than subsequently trying to read the inserted table from cache. This enables my app to always get the table from the caching insert function, even if it is immediately removed from the cache... I'm sure there are more elegant ways to do this but my app now works under heavy load.

Here's a stripped-down outline of what I ended up doing:

dbcache.getcachedtable("mytable", CacheItemPriority.Normal)
dbcache.getcachedtable("mytable", CacheItemPriority.NotRemovable)

class dbachce
public shared function getcachedtable(tablename as string, cachepriority As Caching.CacheItemPriority = CacheItemPriority.Normal) as datatable
Dim dt As DataTable = Nothing
If HttpContext.Current.Cache(tablename) Is Nothing Then
      Dim ld As New dbcacheloader
            dt = ld.loadcachetable(tablename,cachepriority)
            dt = CType(HttpContext.Current.Cache(tablename), DataTable)
End If
end function
end class

class dbcacheloader
public function loadcachetable(tbl_command as string="", cachepriority As Caching.CacheItemPriority = CacheItemPriority.Normal)

Dim cacheexpires As DateTime
If HttpContext.Current.Application("cacheexpires") = Nothing Or HttpContext.Current.Application("cacheexpires") <= DateTime.Now Then
      cacheexpires = DateTime.Now.AddMinutes(30)
      HttpContext.Current.Application("cacheexpires") = cacheexpires
      cacheexpires = HttpContext.Current.Application("cacheexpires")
End If

gettable = DB.GetTable("select * from " & tbl_command) 'class to open connection, retrieve datatable

'inserts the table into cache
HttpContext.Current.Cache.Insert(tbl_command, gettable, Nothing, cacheexpires, TimeSpan.Zero, cachepriority, Nothing)
'returns the table that was retrieved to the caller, just in case item was cleared immediately from cache due to low memory
Return gettable
end function
end class

Thanks Rejojohny for clearing my head. I was looking for a complicated solution to a simple problem.
you are welcome, glad to be of help .. :-)

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.