Actually, my next step was to set up two Hashtable collections, one for clicks and one for impressions, each holding an integer value keyed off of a date plus an advertisement ID key. This would have worked, but since both sets of data are destined for the same database table, it would have required me to reconcile the two sets of counters prior to persisting to the database, or else required a lot of duplicate database calls.
So then, finally, I created an Activity class to hold the ID, date, impressions, and clicks, and then I created a 1400-line strongly-typed ActivityCollection class, which was basically a Hashtable with a strongly-typed String key and a strongly-typed Activity value. At that point, whenever I needed to log an impression or click, it was just a matter of adding a new item to this collection (if I didn’t already have one) or incrementing the existing item’s counter.
Incidentally, I’m not going to list the code for the collection, but I will tell you that I created it automatically using a built-in CodeSmith template (CodeSmith is free, so if you don’t have it already, get with the program and go download it now).
However, since this is meant to perform properly in a high-volume, multi-user environment, I had to consider multi-threaded access. Specifically, I had to implement a locking system that ensured that two requests did not try to increment the exact same item at the same time (a simple example of what happens without locking can be found here).
Now, we could take the naïve approach and simply lock the entire collection, but I know, I just know, that that will result in contention for that resource. (I could do yet another test to show it, but at some point I have to actually implement this code and finish this article.) I figured there had to be a way to do ‘row level’ locking of a Hashtable, and Google quickly spit one out.
Thanks to Ambrose Little and Brian Button for the final locking code shown in Figure 4. For more on how to implement locks check out the MSDN documentation: C# Spec: The lock statement.
Figure 4: LogActivity – Showing Smart Hashtable locking
private static void LogActivity(int featuredItemId, ActivityType activityType)
{
string index = featuredItemId + "-" + DateTime.Today.ToShortDateString();
AspAlliance.Framework.Common.FeaturedItemActivity item =
activityCollection[index];
if(item==null)
{
// only if it is not there do we lock, and then only the
// syncroot, so that other threads can still access the Hashtable
// to read/update other items. Doing this will only block
// threads wanting to add new items to the collection
lock(activityCollection.SyncRoot)
{
// once we get our lock, check again to ensure another thread
// didn't get here first and add it while we were blocking
item=activityCollection[index];
if(item==null)
{
// still null, so we're definitely the first to try
// to add it; create the item and add it.
item = new
AspAlliance.Framework.Common.FeaturedItemActivity(
featuredItemId, DateTime.Today, 0, 0);
activityCollection.Add(index, item);
}
}
}
// now we can just lock the item and do our update
// with only item-level locking.
switch(activityType)
{
case ActivityType.Click :
lock (item) item.Clicks++;
break;
case ActivityType.Impression :
lock (item) item.Impressions++;
break;
}
}
Now that we have a reliable way to store the data on the web server in a high-performing manner, the second step is to figure out a way to write the data to the database.