views:

51

answers:

2

I have a music related ASP.NET web site which caches a lot of static information from the database on the first request.

Sometimes, the application is reset and cache is cleared while the application is on heavy load and then all http requests go to the database to retrieve that static data and cache it for other requests.

How can I ensure that only one request go to the database and cache the results, so that other request simply read that info from cache and not needlessly retrieve the same info over and over again.

Can I use thread locking? For example, can I do something like lock(this) { db access here }?

+2  A: 

Yes, in your caching code, you'll want to put your database-accessing code inside a lock block. However, don't lock on this. Typically you'd do something like

private static readonly object staticObjectToLockOn = new object();

...

if (cache[cacheKey] == null)
{
   lock(staticObjectToLockOn)
   {
      // double-check the cache is still null inside the lock
      if (cache[cacheKey] == null)
      {
         // get data from the database, add to cache
      }
   }
}
Graham Clark
Thank you, but can you explain what is "staticObjectToLockOn"?
MikeJ
@MikeJ: you want to provide a variable that all instances of the class can access, i.e. a static object. See the MSDN documentation for more info - http://msdn.microsoft.com/en-us/library/c5kehkcz(VS.10).aspx
Graham Clark
Graham, thanks, now it's clear, accepted. But, i'm wondering, are there any other ideas?
MikeJ
Yes, as Jan Jongboom says, this approach may not scale, particularly if you have multiple servers (a web-farm), and you expect many simultaneous requests. For these scenarios, you would probably want to look at distributed cache solutions.
Graham Clark
Hm, then I'm not really sure what's the help from this lock? I thought that this code will protect the DB access from other threads in the same process? Isn't that static variable shared across threads? I mean, if I get 10 requests in the same millisecond, won't the first one block the 9 others in the same process?
MikeJ
@MikeJ: The static variable will be used once per Application Domain, so once per server. So if you have one server, this code should work fine. With more than one server, you need to think about whether you want each server to have a cache or whether to have a distributed cache accessible by all the servers.
Graham Clark
That's fine and that's how I understood it, but Jan wrote below "with 10 machines and 32 threads each, that's 320 requests" and since you also pointed to that, I got confused. So, to clarify, with 10 servers, and 10 requests per second on each server, I will have 100 requests across the web farm, but still just 10 request to the database?
MikeJ
@MikeJ: That's how I understand it.
Graham Clark
Yeah, I also thought that... I asked another specific question so we'll both see what others say :) http://stackoverflow.com/questions/2997438/will-lock-statement-block-all-threads-in-the-proccess-appdomain
MikeJ
+1  A: 

Point is with using a generic lock, is that you are getting in serious trouble with alot of users, as the thread can be blocked for every cache item when your app starts. Furthermore will every thread still create it's own lock and query (and with 10 machines and 32 threads each, that's 320 requests!)

We us an approach like this (> 10 mio pageviews / day, and memcached):

When grabbing data from the database, we store an 'in progress' message in a similar cachekey (f.e. adding in-progress to the cachekey or something). When another thread cannot find the current item, it will check the in progress key. If the item is in progress, wait a couple of ms. and try to fetch again. If after a couple of moments (500 ms. ?) do the database action again from the current thread, to protect yourself from killing the app when an item keeps on 'in progress'.

This way you can create some sort of lock, but over multiple machines etc.

Without doing this, our CMS server will get like 200 of the same requests within the first second, so we really need cluster-wide locks.

Jan Jongboom
Not sure how you wait? Thread.Sleep() in a while loop? Isn't that also really dangerous and could possibly hang the process/thread?
MikeJ
Yes, just a `Thread.Sleep()`, not really dangerous.
Jan Jongboom
Ah, now I understand... I will lock just the current thread, but I will probably have 32 threads on the machine, so I will still issue at least 32 db requests per machine? Can I somehow check how many threads my app is using? Thanks for the warning, good point
MikeJ
No, because you put a centralized lock. Graham's solution locks only the current thread; spawning 32 (depends on machine) db actions if fully loaded. By putting the lock into your caching system you avoid this. The thread.sleep() is to make sure you **don't** do 32 requests.You can btw do a process dump to get the number of threads and what they're doing, but don't know how. We have application management team that creates those :-)
Jan Jongboom
@Jan Jongboom: I thought that locking on a static variable affects the whole Application Domain, not just a single thread. So for a single web server, all requests would effectively be accessing the same variable.
Graham Clark
Yeah, sorry, I see now you are using a `static` variable for locking. In that case the whole domain accesses the same var.
Jan Jongboom