This post is migrated from previous hosting provider. There are still some issues with old posts. Please make a comment on this post with any issues.

Improve performance of your SharePoint 2010 applications using Windows Server AppFabric caching

Tags: SharePoint 2010, AppFabric

Besides SharePoint my very dear topics is performance optimizations and new technologies, so here's a post mixing all these together.

Background

Caching is one way to improve the performance of any application, there are several ways to do it in-memory, disk etc etc. SharePoint 2010 has a set of caching capabilities, most of them are in-memory caches and some involve disk or even SQL based. One problem with (especially) in-memory caching is that if you have a farm different servers may display different results, which is due to the fact that the different servers cached information at different times. Another problem with in-memory caching is that it's per process, that is that you have different caches for different web applications and application pools.

Windows Server AppFabric is an extension to Windows Server. There's one cloud version of AppFabric in Windows Azure but there's also an on-premise installation of AppFabric that contains hosting and caching capabilities. AppFabric is a middle-tier service platform (the real middle-tier, not the other middle-tier :-) and can be used in all kinds of scenarios. The caching features of AppFabric is based on the Velocity project, which was one of my favorite acts for the PDC08 conference. It contains an easy to set up and configure distributed caching framework that can use SQL Server or disk as storage. In this post I will show you how to leverage the caching features of the on-premise version of AppFabric.

Using a distributed cache such as AppFabric can solve many problems. I've already mentioned a few such as the problems with in-memory caching. Other interesting things is that you can easily set up cross farm caching or even cross domain caching. You can have the caching on separate servers in the application tier. You can share the cache between web applications and service applications. The cache isn't tied to the application pool so any recycles of that will not clear the cache and so on...

Setting up and configuring AppFabric v1.1 CTP

AppFabric v1.1 CTP is really easy to setup. You install the binaries (download 1.0 via Web Platform installer or 1.1 directly here) on the first machine to host the distributed cache and configure the cache. Then you add new servers to that cache cluster. Very much like you do when setting up a SharePoint farm. Follow these instructions on MSDN, you can't fail (which you can't say with SharePoint).

Installing AppFabric

Configuration is used either using the configuration application or using PowerShell (what else did you expect). In this case I configured it to use SQL Server as backend. Using the disk based would ideally be used in cross farm/domain scenarios (think about that - having data up to date cross farms, that's cool!). Once AppFabric is installed you need to start the AppFabric caching  using the following PowerShell commands. First use Use-CacheCluster to set the context and then just Start-CacheCluster to start the cache. There is one final configuration that is needed and that is to allow the application pool account to access the cache. This is done using the Grant-CacheAllowedClientAccount cmdlet. If you forget to set this you will see an ERRCA0017 error. AppFabric contains tons of configuration options. You could setup different clusters, named caches etc etc.

Using the cache in a Web Part

Now on to the SharePoint bits. In this scenario I'll build a Web Part that fetches quotes from Yahoo, that is doing an HTTP request to get a quote. This is a common scenario where you would build some kind of cache to avoid HTTP latencies. Another problem with HTTP requests is that they might time out or take a long time. You need to call it at least once so the poor bastard that's loading your page first might suffer from it. In this case we could actually create a timer job that makes the requests and put the data in the AppFabric cache before it's requested. But for this simple demo let's settle with a Web Part.

Preparing your solution

Before building the actual Web Part we need to set up the project to use the AppFabric cache. First two references must be added; Microsoft.ApplicationServer.Caching.Core and Microsoft.ApplicationServer.Caching.Client. They are found in the c:\Windows\System32\AppFabric\ folder (even though some documentation says otherwise). I could not directly add them as reference in Visual Studio, it could not find the directory, so I copied the file from that folder to a custom folder first.

After adding the references we need to set up the configuration of our custom cache. Normally you do this using the application configuration file, but in SharePoint scenarios this doesn't work that well, so I'll do it all in code. In this sample I use hardcoded values for servers and ports - in a real world scenario you should of course make this configurable using for instance the P&P SharePoint Guidance Hierarchical Object Store. To access the cache we need to create a cache factory and retrieve our cache from that. Creating the cache factory object is an expensive operation so I'll implement this in a singleton thread-safe object to avoid creating the factory that many times. It looks as follows:

sealed class CacheUtility {
    private static DataCacheFactory _factory;
    private static DataCache _cache;
    private static readonly object _lock = new object();

    CacheUtility() { }

    public static DataCache CurrentCache     {
        get {
            using (SPMonitoredScope scope = new SPMonitoredScope("DataCache.CurrentCache")) {
                lock (_lock) {
                    if (_cache == null) {
                        List<DataCacheServerEndpoint> servers =                             new List<DataCacheServerEndpoint>();
                        servers.Add(new DataCacheServerEndpoint("SP2010", 22233));
                        DataCacheFactoryConfiguration config =                             new DataCacheFactoryConfiguration() {
                            Servers = servers
                        };
                        _factory = new DataCacheFactory(config);
                        _cache = _factory.GetDefaultCache();
                    }
                    return _cache;
                }
            }
        }
    }
}

As you can see in this class I have the DataCacheFactory object and the actual cache object (DataCache) as static variables. They are only created on the first request to the CurrentCache property. The DataCacheFactory object is instantiated with a configuration, in this case I'll just add one AppFabric cache server using the server name and the cache port.

Then let's add the Web Part to the project. In the Web Part class implement the function to get the stock quotes as follows:

public class Quote {
    public string Name;
    public double LastTrade;
    public DateTime DateTime = DateTime.Now;
}

public Quote GetQuote(string ticker) {
    using (SPMonitoredScope scope = new SPMonitoredScope("GetQuote")) {
        using (WebClient client = new WebClient()) {
            string tmp = client.DownloadString(                String.Format("http://finance.yahoo.com/d/quotes.csv?s={0}&f=nl1", ticker));
            string[] arr = tmp.Split(new char[] { ',' });
            if (arr.Length == 2) {
                return new Quote() {
                    Name = arr[0].Trim(new char[] { '"' }),
                    LastTrade = double.Parse(arr[1])
                };
            }
        }
        return new Quote() {
            Name = "Not found"
        };
    }
}

It's a simple method, GetQuote(), that retrieves the stock quote using the Yahoo Finance API. If the quote is found it returns a Quote object with correct values and if not an "empty" Quote object.

Also add a property to the Web Part so that we can turn on and off the caching:

[Personalizable(PersonalizationScope.Shared)]
[WebBrowsable]
[WebDisplayName("Use Cache")]
public bool UseCache { get; set; }

Now it's time to implement the actual Web Part, and as always it's done in the CreateChildControls, like this:

protected override void CreateChildControls() {
    using (SPMonitoredScope scope = new SPMonitoredScope("CachedWebPart.CreateChildControls")) {
        Quote quote = null;
        string ticker = "MSFT";
        if (UseCache) {
            DataCacheItem cacheItem = CacheUtility.CurrentCache.GetCacheItem("quote" + ticker);
            if (cacheItem != null) {
                quote = (Quote)cacheItem.Value;
            }
        }
        if (quote == null) {
            quote = GetQuote(ticker);
            if (UseCache) {
                CacheUtility.CurrentCache.Add("quote" + ticker, quote, new TimeSpan(0, 1, 0));
            }
        }
        this.Controls.Add(new LiteralControl(
            String.Format("Last trade for {0} is {1}, retrieved at {2}", 
                quote.Name, quote.LastTrade, quote.DateTime)));
    }
}

This method is also pretty straight forward. It checks if the cache is enabled and if it is it tries to retrieve the quote from the cache using the GetCacheItem method. If it's not found it uses the GetQuote method to retrieve the quote and if the cache is enabled it stores the quote in the cache. Finally it just prints out the quote value. That's it!

Test and evaluate

So let's take a look at this Web Part now using the Developer Dashboard (notice the SPMonitoredScopes I added to the code). Here's how it looks without the cache enabled. The call to the Yahoo Finance API takes about half a second - a real performance killer.

No caching

Once we turn on the caching, the first call to the CacheUtility class will initialize the cache and for subsequent calls use that cached factory and cache. Notice how the first hit which creates the cache actually takes some time and in this case the HTTP request takes a really long time!

Caching started

Then once the cache is up and running, retrieving the quote takes almost no time.

Caching in full effect

Improve performance a little bit more...

But it still takes about 10ms! This 10ms comes from that this is not an in-memory cache, it is actually stored in SQL Server. But hey, it's better than a couple of seconds. But I want to save an extra couple of cycles here. Ok, let's enable the client cache! Add the following row when configuring the cache factory and you will also have an in-memory cache that has a maximum number of object, a timeout and an invalidation policy.

config.LocalCacheProperties = 
    new DataCacheLocalCacheProperties(
        100, 
        new TimeSpan(0, 3, 0), 
        DataCacheLocalCacheInvalidationPolicy.NotificationBased);

In this case I set the invalidation policy to use notifications. To get this up and running you need to shut down the cache cluster and configure notifications for the cluster as follows:

Stop-CacheCluster 
Set-CacheConfig -CacheName "default" -NotificationsEnabled $true 
Start-CacheCluster

And now retry the Web Part and we'll see that this is more efficient:

Client cache enabled

Monitoring

The AppFabric cache also contains a lot of features for monitoring and logging. The simplest command is the Get-CacheStatistics that show you stats of the cache:

Cache stats

Summary

Next time you are thinking about caching in SharePoint (or any other .NET application for that matter) consider using AppFabric caching. It's a great and versatile middle-tier caching framework with near endless configuration possibilities. I know that this simple yet powerful framework can make a big difference in any of your projects. And why invent some own caching framework when we have something beautiful as this!

No Comments

Comments have been disabled for this content.

About Wictor...

Wictor Wilén is the Nordic Digital Workplace Lead working at Avanade. Wictor has achieved the Microsoft Certified Architect (MCA) - SharePoint 2010, Microsoft Certified Solutions Master (MCSM) - SharePoint  and Microsoft Certified Master (MCM) - SharePoint 2010 certifications. He has also been awarded Microsoft Most Valuable Professional (MVP) for seven consecutive years.

And a word from our sponsors...