ASP.NET MVC Tip #39 – Use the Velocity Distributed Cache

Improve the performance of ASP.NET MVC applications by taking advantage of the Velocity distributed cache. In this tip, I also explain how you can use Velocity as a distributed session state provider.

The best way to improve the performance of an ASP.NET MVC application is by caching. The slowest operation that you can perform in an ASP.NET MVC application is database access. The best way to improve the performance of your data access code is to avoid accessing the database at all. Caching enables you to avoid accessing the database by keeping frequently accessed data in memory.

Because ASP.NET MVC is part of the ASP.NET framework, you can access the standard ASP.NET System.Web.Caching.Cache object from your ASP.NET MVC applications. The standard ASP.NET Cache object is a powerful class. You can use the Cache object to cache data in memory for a particular period of time, create dependencies between items in the cache and the file system or a database table, and create complicated chains of dependencies between different items in the cache. In other words, you can do a bunch of fancy things with the standard ASP.NET Cache object.

The one limitation of the standard ASP.NET Cache object is that it runs in the same process as your web application. It is not a distributed cache. The same ASP.NET Cache cannot be shared among multiple machines. If you want to share the same ASP.NET Cache among multiple machines, you must duplicate the cache for each machine.

The standard ASP.NET Cache works great for web applications running on a single server. But what do you do when you need to maintain a cluster of web servers? For example, you want to scale your web application to handle billions of users. Or, you don’t want to reload the data in the cache when a server fails. In situations in which you want to share the same cache among multiple web servers, you need to use a distributed cache.

In this tip, I explain how you can use the Microsoft distributed cache (code-named Velocity) with an ASP.NET MVC application. Setting up and using Velocity is surprisingly easy. Switching from the standard ASP.NET Cache to the Velocity distributed cache is surprisingly painless.

I’m also going to explain how you can use Velocity as a session state provider. Velocity enables you to use ASP.NET session state even when you are hosting an MVC application on a cluster of web servers. You can use Velocity to store session state in the distributed cache.

Installing and Configuring Velocity

As I write this, Velocity is not a released product. It is still in the Community Technology Preview state of its lifecycle. However, you can download Velocity and start experimenting with it now.

The main website for all information on Velocity is located at the following address:

http://msdn.microsoft.com/en-us/data/cc655792.aspx

You can download Velocity by following a link from this web page.

Installing Velocity is a straightforward process. You need to install Velocity on each server where you want to host the cache (each cache server). The cache is distributed across these servers.

Before you install Velocity on any of the cache servers, you need to create one file share in your network that is accessible to all of the cache servers. This file share will contain the cache configuration file (ClusterConfig.xml). The cache configuration file is an XML file that is used to configure the distributed cache.

During this Beta period, you must give the Everyone account Read and Write permissions to the file share that contains the configuration file. Right-click the folder, select the Properties menu option and select the Security tab. Click the Edit button, click the Add button, and enter the Everyone account and click the OK button. Make sure that the Everyone account has Read and Write permissions.

When you run the installation program for Velocity (see Figure 1), you will be asked to enter the following information:

· Cluster Configuration Share – This is the path to the folder share that we just created.

· Cluster Name – The name of the cluster. Enter any name that you want here (for example, MyCacheCluster).

· Cluster Size – The number of cache servers that you expect to use in this cluster.

· Service Port Number – The port that your application uses to communicate with the cache server (needs to be unblocked from your firewall).

· Cluster Port Number -- The port that the cache servers use to communicate with each other. Velocity uses this port number and an additional port located plus one port higher (both of these ports need to be unblocked from your firewall).

· Max Server Memory – The maximum amount of memory used by Velocity on this server.

Figure 1 – Installing Velocity

clip_image002

After you install Velocity on each of the servers, you must configure firewall exceptions for Velocity on each server. If you don’t, then communication with Velocity will be blocked. You can create a firewall exception for the DistributedCache.exe program name or you can create the firewall exceptions for each port number (port 22233, port 22234, and port 22235).

Using the Velocity Administration Tool

You manage Velocity through a command line administration tool which you can open by selecting Start, All Programs, Microsoft Distributed Cache, Administration Tool (see Figure 2).

Figure 2 – The Velocity Administration Tool

clip_image004

The Administration Tool supports the following important commands (these commands are case sensitive):

· start cluster – Starts Velocity for each cache server in the cluster

· stop cluster – Stops Velocity for each cache server in the cluster

· create cache – Creates a new named cache

· delete cache – Deletes an existing cache

· list host – Lists all of the cache servers in the cluster

· list cache – Lists all of the caches configured in the cache cluster

· show hoststats <cache server>:<cache port>– Lists statistics for a particular cache server

The first thing that you will want to do after installing Velocity is to start up the cache cluster. Enter the following command:

start cluster

Using Velocity with an ASP.NET MVC Application

After you have Velocity up and running, you can build an ASP.NET MVC application that uses the Velocity cache. In order to use Velocity in a project, you will need to add references to the following assemblies:

· CacheBaseLibrary.dll

· ClientLibrary.dll

You can find these assemblies in the Program Files\Microsoft Distributed Cache folder on any machine where you installed Velocity. You can copy these assemblies from the cache server to your development server.

You also need to modify your ASP.NET MVC application’s web configuration (web.config) file. Add the following section handler to the <configSections> section:

<section name="dcacheClient"
    type="System.Configuration.IgnoreSectionHandler"
    allowLocation="true" allowDefinition="Everywhere"/>

Next, add the following section anywhere within your configuration file (outside of another section):

<dcacheClient deployment="simple" localCache="false">
<hosts>
   <!--List of hosts -->
   <host name="localhost"
      cachePort="22233"
      cacheHostName="DistributedCacheService" />
   </hosts>
</dcacheClient>

You might need to change one value here. This section lists one cache host with the name localhost. In other words, the MVC application will contact the cache server on the local computer. If the cache server is located somewhere else on your network, you’ll need to change name to point at the right server.

Adding and Retrieving Items from the Cache

Using the Velocity distributed cache is very similar to using the normal ASP.NET cache. You can use the following methods to add, retrieve, and remove items from the distributed cache:

· Add() – Adds a new item to the distributed cache. If an item with the same key already exists then an exception is thrown.

· Get() – Gets an item with a certain key from the distributed cache.

· Put () – Adds a new item to the distributed cache. If an item with the same key already exists then the item is replaced.

· Remove() – Removes an existing item from the distributed cache.

Let’s look at a concrete sample. The MVC controller in Listing 1 uses the distributed cache to cache a set of Movie database records.

Listing 1 – HomeController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Data.Caching;
using Tip39.Models;
using System.Diagnostics;
using System.Web.Configuration;
using System.Web.Hosting;
using System.Data.Linq.Mapping;
using System.Data.Linq;

namespace Tip39.Controllers
{
    [HandleError]
    public class HomeController : Controller
    {
        private DataContext _dataContext;
        private Table<Movie> _table;

        public HomeController()
        {
            // Get connection string
            var conString = WebConfigurationManager.ConnectionStrings["Movies"].ConnectionString;

            // Get XML mapping source
            var url = HostingEnvironment.MapPath("~/Models/Movie.xml");
            var xmlMap = XmlMappingSource.FromUrl(url);

            // Create data context
            _dataContext = new DataContext(conString, xmlMap);
            _table = _dataContext.GetTable<Movie>();
        }


        public ActionResult Index()
        {
            // Try to get movies from cache
            var factory = new CacheFactory();
            var cache = factory.GetCache("default");
            var movies = (List<Movie>)cache.Get("movies");
           
            // If fail, get movies from db
            if (movies != null)
            {
                Debug.WriteLine("Got movies from cache");
            }
            else
            {
                movies = (from m in _table select m).ToList();
                cache.Put("movies", movies);
                Debug.WriteLine("Got movies from db");
            }

            // Display movies in view
            return View("Index", movies);
        }

    }
}

The Index() method returns all of the rows from the movies database table. First, the method attempts to retrieve the records from the cache. If that fails, the records are retrieved from the database and added to the cache.

The Index() method calls Debug.WriteLine() to write messages to the Visual Studio Console window. When the records are retrieved from the distributed cache, this fact is reported. When the records are retrieved from the database, this fact is also reported (see Figure 2).

Figure 2 – Using the Visual Studio Console to track cache activities

clip_image006

You also can use the Velocity Administration Tool to monitor the distributed cache. Executing the following command displays statistics on the distributed cache:

show hoststats server name:22233

Replace server name with the name of your cache server (unfortunately, localhost does not work). When you run this command, you get super valuable statistics on the number of items in the cache, the size of the cache, the number of times a request has been made against the cache, and so on (see Figure 3).

Figure 3 – Cache Statistics

clip_image008

The Index() method in Listing 1 first creates an instance of the CacheFactory class. The CacheFactory class is used to retrieve a particular cache from the distributed cache.

Velocity can host multiple named caches. You can group different data into different caches. Think of each named cache like a separate database. When you first install Velocity, you get one named cache named “default”. In Listing 1, the CacheFactory is used to retrieve the “default” cache.

Next, the Cache.Get() method is used to retrieve the movie database records from the cache. The first time the Index() method is invoked, the Get() method won’t return anything because there won’t be any data in the distributed cache.

If the Get() method fails to return any database records from the cache then the records are retrieved from the actual database. The records are added to the distributed cache with the help of the Put() method.

Notice that the Cache stores and retrieves items as untyped objects. You must cast items retrieved from the cache to a particular type before using the items in your code. Most likely, when using the distributed cache in a production application, you’ll create a strongly typed wrapper class around the distributed cache.

Also, be aware that the distributed cache survives across Visual Studio project rebuilds. If you want to clear the distributed class while developing an application that interacts with the cache, execute this sequence of commands from the Velocity Administration Tool:

stop cluster

start cluster

What can be Cached?

You can add any serializable class to the distributed cache. That means that you can add any class that is marked with the [Serializable] attribute (all of the dependent classes of a serializable class must also be serializable).

The Home controller in Listing 1 uses LINQ to SQL to grab database records from the database. You must be careful when using LINQ to SQL with the distributed cache since, depending on how you create your LINQ to SQL classes, the classes won’t be serializable.

If you use the Object Relational Designer to generate your LINQ to SQL classes then your LINQ to SQL classes will not be serializable. To work around this problem, I built my LINQ to SQL classes by hand. The Movie.cs class is contained in Listing 2.

Listing 2 – Models\Movie.cs

using System;

namespace Tip39.Models
{
    [Serializable]
    public class Movie
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Director { get; set; }
        public DateTime DateReleased { get; set; }
    }
}

Notice that the Movie class includes a [Serializable] attribute.

The XML mapping file in Listing 3 is used in the HomeController constructor to initialize the LINQ to SQL DataContext. This XML mapping file maps the Movie class and its properties to the Movies table and its columns.

Listing 3 – Models\Movie.xml

<?xml version="1.0" encoding="utf-8" ?>
<Database Name="MoviesDB" xmlns="http://schemas.microsoft.com/linqtosql/mapping/2007">
<Table Name="Movies" Member="Tip39.Models.Movie">
    <Type Name="Tip39.Models.Movie">
        <Column Name="Id" Member="Id" IsPrimaryKey="true" IsDbGenerated="true"/>
        <Column Name="Title" Member="Title" />
        <Column Name="Director" Member="Director" />
        <Column Name="DateReleased" Member="DateReleased" />
    </Type>
</Table>
</Database>

One other warning about using LINQ to SQL with the distributed cache. Realize that a LINQ to SQL query is not actually executed against the database until you start iterating through the query results. Make sure that you add the results of a LINQ to SQL query, instead of the query itself, to the cache.

In the Index() method in Listing 1, the movie records are added to the cache with the following two lines of code:

movies = (from m in _table select m).ToList();
cache.Put("movies", movies);

Notice that the ToList() method is called on the LINQ to SQL query. Calling the ToList() method forces the query to be executed against the database so that the actual results are assigned to the movies variable instead of the LINQ to SQL query expression.

Using Velocity as a Session State Provider

By default, the ASP.NET framework stores session state in the same process as your ASP.NET MVC application. Storing session state in-process provides you with the best performance. However, this option has one huge drawback. You can’t host your ASP.NET MC application on multiple web servers when using in-process session state.

The ASP.NET framework provides you with two alternatives to in-process session state. You can store your session state information with a state server (a Windows service) hosted on a machine in your network. Or, you can store your session state information in a SQL Server database. Both of these options enable you to create a central session state server so that you can scale your ASP.NET MVC application up to use multiple web servers.

There are disadvantages associated with using either the state server or the SQL Server approach to storing session state. The first option, the state server approach, is not very fault tolerant. The second option, the SQL Server option, is not very fast (each request made against your MVC application causes a database query to be executed to retrieve session state for the current user).

Velocity provides you with a better option for storing out-of-process session state. You can store your state information in the Velocity distributed cache. Unlike the state server approach, the distributed cache is fault tolerant. Unlike the SQL Server approach, the Velocity cache is entirely in-memory so it is comparatively fast.

Imagine that you want to keep count of the number of times that a particular user has visited a web page (see Figure 4). You can store a count of visits in session state. The Counter controller in Listing 4 maintains a count of visits with an item in session state named count.

Figure 4 -- Keeping count of page views

image

Listing 4 – CounterController.cs

using System.Web.Mvc;

namespace Tip39.Controllers
{
    public class CounterController : Controller
    {
        public ActionResult Index()
        {
            var count = (int?)Session["count"] ?? 0;
            count++;
            Session["count"] = count;
            return View("Index", count);
        }
    }
}

By default, the Counter controller will use in-process session state. If we want to use the Velocity session state store then we need to modify our web configuration (web.config) file. You need to add the <sessionState> information in Listing 5 within the <system.web> section.

Listing 5 – Session Configuration for Velocity

<sessionState mode="Custom" customProvider="dcache">
<providers>
  <add 
     name="dcache" 
     type="System.Data.Caching.SessionStoreProvider"/>
</providers>
</sessionState>

After you make this change to the web configuration file, session state information is stored in the distributed cache instead of in-process. You can verify that the Counter controller is using the distributed cache by using the Velocity Administration Tool. Executing the following command will display how many times the distributed cache has been accessed:

show hoststats server name:22233

Each time the Counter controller Index() action method is invoked, the Total Requests statistic is incremented by two: once for reading from session state and once for writing to session state. See Figure 5.

Figure 5 – Seeing Total Requests

clip_image010

Notice that you can start using the Velocity session state store without changing a single line of code in your existing application. You can switch session state providers entirely within the web configuration file. This means that you can initially build your application to use in-process session state and switch to the Velocity session state provider in the future without experiencing a single tingle of pain.

Summary

In this tip, I’ve provided you with a brief introduction to the Velocity distributed cache. I demonstrated how you can use the distributed cache in the context of an ASP.NET MVC application to store database records across multiple requests. I also showed you how to take advantage of the Velocity session state provider to store session state in the distributed cache.

In this tip, I’ve only provided the most basic introduction to Velocity. There are many features of Velocity that I have not discussed. For example, Velocity supports a locking mechanism. You can use either optimistic or pessimistic locking when working with Velocity to prevent concurrency violations. You also can use Velocity to tag and search items that you add to the cache.

Even though this was a brief introduction, I hope that I have provided enough detail to prompt you to experiment with Velocity in your ASP.NET MVC applications.

14 Comments

  • No numbers? (performance wise)

  • You made the comment, "each time you retrieve an item from session state causes a database query to be executed". This is not true. For session state the entire session is loaded when the application event AcquireRequestState is raised. When the application event ReleaseRequestState, the session state is persisted (if dirty). The main performance issue with SQL Server session state is that the entire session is loaded on every request even if not referenced. That is why on the Page directive you can add EnableSessionState="false".

    I think you may have gotten confused with the profile provider which will load profile properties on demand. For each property you reference, it will perform a round trip to the database.

  • @Phil - Thanks for the correction, you are absolutely right. I misspoke.

  • Very well done Stephen. In a world of regergitated (and resyndicated) content, you manage to remain fresh.

    I've been very interested in Velocity for a little while now for non-web applications... I didn't realize how easily it snapped into ASP.NET (I should have figured).

    Thanks again for another great post.

    -Timothy

  • Isn't the one file share a single point of failure. If high availability is important, where can you put the configuration file so that if you lose the server that is sharing the file, you won't lose the cache?

  • @High Availability - Yes, the file share is a single point of failure. The docs hint that this is something that will change before the final release.

  • @dswatik - That's a really interesting article. Thanks for the reference to it.

  • hopefully velocity ctp2 will be faster then it runs with ctp 1. my tests shown me that memcache is almoast 3 times faster.

  • it was mentioned before, how does this belong to MVC actually?

    thank you for the "tip" though

  • Velocity maybe a promising prospect but for now it is only in its CTP phase. Why not take a look at NCache and others while we wait? By their own admission Velocity is in its infancy and has a long way to go before it can challenge the prevalent solutions.

  • Asp net mvc tip 39 use the velocity distributed cache.. Keen :)

  • Asp net mvc tip 39 use the velocity distributed cache.. Smashing :)

  • Asp net mvc tip 39 use the velocity distributed cache.. Great! :)

  • IMHO you've got the right aenswr!

Comments have been disabled for this content.