Cache Management in ASP.NET

Introduction

Before explaining cache management in ASP.NET, let me clarify that different people use different terms for explaining the same concept i.e. managing data. Some people refer to it as state management and some others refer to it as cache management. I love to use the term cache management, may be because I like the word "cache". But conceptually there is no difference between these two. Now let's discuss different aspects of cache management (or state management) in ASP.NET.


Although cache management is not an issue in Windows applications, it has always been a challenge in the web environment. Since HTTP is a stateless protocol and a web server doesn't recognize users between different requests, it becomes very important for us to recognize a particular user between different requests and also store data so that it can be re-used between different requests. ASP.NET provides many features for storing data both in the client (browser) and the server (web server) sides, but sometimes we get confused with when to use what. In ASP.NET we come across features like Session, Application and Cache objects, but it is important for us to understand the difference between them in order to effectively use them in web applications.

Background

In this article, I will touch upon the different cache management options available in ASP.NET. In a web application, sometimes we require to store data in the server side to avoid costly data retrieval operation from data stores and time consuming data formatting logic to improve application performance as well as to re-use the same data in subsequent requests across users, applications and machines. So, to achieve this we need to store (cache) data in the server side.
Caching helps us to achieve three important aspects of QoS (Quality Of Service):
  • Performance - Caching improves application performance by minimizing data retrieval and formatting operations.
  • Scalability - Since caching minimizes data retrieval and formatting operations, it reduces the load on server resources thus increasing the scalability of the application.
  • Availability - Since the application uses data from a cache, the application will survive failures in other systems and databases.

Different options

In a web application, we can cache data, pages etc. both in server and client sides. Let us have a look at the different options available in ASP.NET for caching data both in server and client sides.

Server side cache management

ASP.NET session state

ASP.NET session state is used to cache data per user session. It means that the data cannot be shared across multiple users and the data usage is limited to the user session it is created in. ASP.NET Session object is used for this purpose.
ASP.NET session state can be managed in three different ways:
  • InProc - Stored in the aspnet_wp.exe process area. The session data is lost when the process or the application domain is recycled.
  • StateServer - Session state is stored in a separate process (aspnet_state.exe) which can be stored in a different machine. Since it can be stored in a different machine, this option will work in a web farm scenario.
  • SQLServer - Session state is stored in a SQL Server database. This option will also work in a web farm scenario.
Both in StateServer and SQLServer options, we need to ensure that the objects we cache are serializable as data storages are out-of-process systems. Both these options have impact on the application performance as data retrieval and saving operations take more time when compared to the InProc option. So based on our application requirement we should choose the option that best suits our requirement.
The following example shows how the Session object can be used in the code:




string empNum = Request.QueryString["empnum"];
if (empNum != null)
{
    string details = null;
 
    if (Session["EMP_DETAILS"] == null)
    {
        //Get Employee Details for employee number passed
        string details = GetEmployeeDetails(Convert.ToInt32(empNum));
 
        Session["EMP_DETAILS"] = details;
    }
    else
    {
        details = Session["EMP_DETAILS"];
    }
 
    //send it to the browser
    Response.Write(details);
}

ASP.NET application object

ASP.NET provides an object called Application object to store data that is accessible to all user requests. The life span of this object is tied to the application and it is re-created every time the application starts. Unlike ASP.NET Session object this object is accessible to all user requests. Since this storage is created and maintained in an application domain space, this should not be used for data storage in a web farm scenario. This option is very useful to store data like the application metadata (CONFIG files data) that can be loaded to the Application object during application start up and can be used during the life of the application without reloading it every time for each user request. But if there is a requirement to invalidate the cached data whenever there is any change to the CONFIG files while the application is running, this option should not be used as it doesn't provide any feature to expire the cached data. So in this case other options like the ASP.NET Cache object should be used, which is explained below.

ASP.NET cache object

ASP.NET cache object is my favorite caching mechanism. That's why I love to talk more about it. ASP.NET provides a key-value pair object - the Cache object which is available in the System.Web.Caching namespace. The scope of it is the application domain and the life span is tied to the application. Unlike the ASP.NET Session object, this object is accessible to all user requests.
Although both Application and Cache objects look the same, the key difference between them is the added features provided by the Cache object like the expiration policies and dependencies. It means that the data stored in the cache object can be expired/removed based on some predefined time limit set by the application code or when the dependent entity gets changed whereas this feature is not available in the Application object.
Let us discuss the different expiration policies and the dependencies that are supported.
Dependency
Dependency means that an item can be removed from the cache when a dependent entity gets changed. So a dependent relationship can be defined on an item whose removal from the cache will depend on the dependent. There are two types of dependencies supported in ASP.NET.
  • File dependency - This provides an option to remove an item automatically from the cache whenever a disk file changes. Let's say in my application I am using an XML file to store error details (error number and error message mapping) which is used to retrieve an error message for a given error number at runtime. So instead of reading the file from the disk each time I need an error message, let's say I decide to load it once at application startup and store it in the ASP.NET cache for further use. So, if I need to change the CONFIG file to add new error messages or change some of the existing error messages while the application is running, then what will happen to my cached data? Do I need to stop the application and start it again to reflect the file changes in the application? The answer is no. The cache data can be invalidated whenever the file changes by using the File dependency option.
The following example shows how File dependency can be used to invalidate the cache item. So whenever the errors.xml file changes the cache item will automatically get expired and will be removed from the cache.




 object errorData;
 //Load errorData from errors.xml 
 CacheDependency fileDependency = 
     new CacheDependency(Server.MapPath("errors.xml")); 
 Cache.Insert("ERROR_INFO", errorData, fileDependency);
  • Key dependency - Key dependency is similar to file dependency but the only difference is that instead of a file the item is dependent on another item in the cache and gets invalidated when the dependent item gets changed or removed. This option is useful when multiple related items are added to the cache and those items are to be removed if the primary item is changed. For e.g. employee number, name, address and salary are added to the cache and if the employee number is updated/removed all the related employee information are removed. In this case the employee number dependency can be used for other employee information.
The following example shows how Key dependency can be used to invalidate the cache items.




string[] relatedKeys = new string[1]; 
relatedKeys[0] = "EMP_NUM";
CacheDependency keyDependency = new CacheDependency(null, relatedKeys); 
Cache["EMP_NUM"] = 5435;
Cache.Insert("EMP_NAME", "Shubhabrata", keyDependency);
Cache.Insert("EMP_ADDR", "Bhubaneswar", keyDependency);
Cache.Insert("EMP_SAL", "5555USD", keyDependency);
Expiration policy
Expiration policy sets the policy for how and when an item in the cache should expire.
  • Time based expiration - Time based expiration provides an option to expire an item in the cache at a predefined time. The expiration time can be set as absolute time like 31st October 2005 12:00 or sliding time i.e. relative to the current time when the item is accessed.


//Absolute Expiration
Cache.Insert("EMP_NAME", "Shubhabrata", null, 
             DateTime.Now.AddDays(1), Cache.NoSlidingExpiration);
 
//Sliding Expiration
Cache.Insert("EMP_NAME", "Shubhabrata", null, 
             Cache.NoAbsoluteExpiration, TimeSpan.FromSeconds(60));
How to know when an item is removed?
In the above examples we learned how to remove an item from the cache but sometimes we want to know when an item is removed from the cache. Yes, it is possible by using cache callbacks. In the error details example shown above, the cached item expires whenever the errors.xml file changes. Suppose we want to update the cache with the latest error details. To find out when the error details are removed from the cache we can use cache callback for further processing (reloading the error details again to the cache).
The following example shows how to use cache callback to handle the item expiration scenario:




private void AddItemsToCache()
{    
    int empNum = 5435;
    CacheItemRemovedCallback onEmpDetailsRemove = 
                    new CacheItemRemovedCallback(EmpDetailsRemoved);
    Cache.Insert("EMP_NUM", empNum, null, 
                              Cache.NoAbsoluteExpiration, 
                              Cache.NoSlidingExpiration, 
                              CacheItemPriority.Default, 
                              onEmpDetailsRemove);
}
 
private void EmpDetailsRemoved(string key, object val, 
                              CacheItemRemovedReason reason)
{
    //When the item is expired
    if (reason == CacheItemRemovedReason.Expired)
    {
        //Again add it to the Cache
        AddItemsToCache();

    }
}
In the above example you must have noticed the parameter CacheItemPriority used along with the callback method. CacheItemPriority is used to set the priority of the item that we are adding to the cache. The priority needs to be set to tell the cache that incase of low memory in the server remove the cached items to free memory based on the priority set. This process is known as scavenging.

.NET remoting

You might be thinking how .NET remoting can be used for data caching? The same question came to my mind when I heard about it for the first time. As you know the .NET remoting singleton object shares the same instance with multiple clients so singleton objects can be used to store and share data between different client invocations. Since .NET remoting can be used outside the process and machine, this option is very useful when we want to cache data and share it across servers and users particularly in a web farm scenario. In this approach we can store the data as member variables of singleton remoting object and provide methods to read and save data. But while implementing this we need to ensure that the remoting object used as cache is not destroyed by the garbage collector. For that we will have to set the remoting cache object's lease period to infinite so that the lease period never times out. We can do that by overriding the InitializeLifetimeService method of MarshalByRefObject and return null from this method. But the only issue with this approach is performance. As the analysis says the performance is very poor in this approach when compared to other approaches. Anyway it is up to the application designers/developers to analyze different options and use the best one that suits the requirement.

Memory-mapped files

You all know what a memory-mapped file is. It is basically about mapping a file on disk to a specific range of addresses in the application's process address space. This option allows different processes to use the same data by increasing the application performance. As using memory-mapped file is not very popular among .NET developers, I would personally not suggest this approach as implementing this involves a lot of complexities and also .NET Framework doesn't support this. But if anyone is very much interested in using this approach then they will have to develop their own custom solution as per their own requirement.

Static variables

We use static variables for storing data or objects globally so that it can be accessed during the life of the application. Similarly, in ASP.NET we can use static objects for caching data and we can also provide methods to retrieve and save data to the cache. As static variables are stored in the process area, performance wise it is faster. But since it is very difficult to implement expiration policies and dependencies incase of static variables, I generally prefer ASP.NET cache object over this option. Another problem is that the custom static cache object has to be thread-safe which has to be implemented carefully.
Custom static cache object can be defined as shown below:




public class CustomCache
{
    //Synchronized to implement thread-safe
    static Hashtable _myCache = 
             Hashtable.Synchronized(new Hashtable());
    
    public static object GetData(object key)
    {
        return _myCache[key];
    }
    
    public static void SetData(object key, object val)
    {
        _myCache[key] = val;
    }
}

Database

We can also use a database for storing data and sharing the data across users and machines. This approach is very useful when we want to cache large amounts of data. Using this approach for storing small amount of data is not a good idea because of performance. For storing small amount of data we should go for other ASP.NET in-process caching mechanisms. As the data needs to be stored in a database all the objects need to be XML serialized so that it is easier to store and retrieve. We can also use other types of serialization formats available in the .NET Framework.

ASP.NET page output caching

Sometimes in our application in some pages the output generally doesn't change for a specific period of time. For example in a HR website, the salary details of an employee doesn't change very frequently and it changes only once in a month. Generally it changes only on the 1st day of every month. So during a month the salary details page will show the same details for a particular employee. So in this case isn't it a good idea to cache the page somewhere in the server to avoid business calculation processing, calls to database and page rendering logic every time the employee wants to see his salary details. In my opinion, Yes! It is a very good idea. To achieve this, ASP.NET provides a feature to store the output of a page in the server for a specific period of time. It also provides features to store a fragment of a page which is known as Page fragment caching. Here I am not going to talk much about Page Output Caching as there are other articles available in the Internet that cover this mechanism in detail. This would a very lengthy section if I start discussing it. I am planning to cover this in another article.




See full details: http://www.codeproject.com/KB/web-cache/cachemanagementinaspnet.aspx

Comments

Popular posts from this blog

Asynchronous Socket Programming in C#

Url Routing MVC TUTORIAL

WCF Chat Sample