Using Distributed Caching With Agatha

7 commentsWritten on February 27th, 2011 by
Categories: agatha

As you may or may not know, Agatha has supported server-side caching of responses for a while now (it also sports built-in client-side caching actually). But it only came with one in-memory implementation of that cache. And while that implementation works well, it's still just an in-process cache which just isn't sufficient for some scenarios.

This week i was introduced to Membase, a great distributed caching solution which is very easy to set up. I wanted to see what it would take to make Agatha's server-side caching work with Membase. With a little help from the Enyim Membase client, it turned out to be very easy. If you want to change the actual caching implementation that Agatha uses, you have to implement 2 interfaces. First, you'll need a custom implementation of the ICache interface:

    public class MembaseCache : ICache
    {
        private readonly MembaseClient membaseClient;

        public MembaseCache(string region = null)
        {
            // this implementation assumes password-less buckets
            membaseClient = new MembaseClient(region, null);
        }

        public Response GetCachedResponseFor(Request request)
        {
            return membaseClient.Get<Response>(GetKey(request));
        }

        public void Store(Request request, Response response, TimeSpan expiration)
        {
            membaseClient.Store(StoreMode.Set, GetKey(request), response, expiration);
        }

        public void Clear()
        {
            membaseClient.FlushAll();
        }

        private static string GetKey(Request request)
        {
            return string.Format("{0}_{1}", request.GetType().FullName, request.GetHashCode());
        }
    }

With Agatha's caching, you can use regions in your caching configuration. A region corresponds with a bucket in Membase. If you don't specify a region name when configuring caching for a response, Agatha will use the default region which is named _defaultRegion. You will need to create at least the _defaultRegion bucket in your Membase cluster, and you'll also need to create a bucket for each other region you use in your caching configuration. When your service layer is initialized, Agatha will create an ICache instance for each known region to be used.

Then you'll need an ICacheProvider implementation:

    public class MembaseCacheProvider : ICacheProvider
    {
        public ICache BuildCache(string region)
        {
            return new MembaseCache(region);
        }
    }

Now, because the Enyim Membase client uses binary serialization of cached objects by default, we're going to provide our own ITranscoder (defined in the Enyim assembly) implementation which uses the DataContractSerializer:

    public class MembaseTranscoder : ITranscoder
    {
        public CacheItem Serialize(object o)
        {
            using (var stream = new MemoryStream())
            {
                var serializer = new DataContractSerializer(typeof(Response), KnownTypeProvider.GetKnownTypes(null));
                serializer.WriteObject(stream, o);
                var data = stream.ToArray();

                return new CacheItem(((ushort)(((ushort)Type.GetTypeCode(o.GetType())) | 0x100)),
                    new ArraySegment<byte>(data, 0, data.Length));
            }
        }

        public object Deserialize(CacheItem item)
        {
            using (var stream = new MemoryStream(item.Data.Array.Skip(item.Data.Offset).ToArray()))
            {
                var serializer = new DataContractSerializer(typeof(Response), KnownTypeProvider.GetKnownTypes(null));
                return serializer.ReadObject(stream);
            }
        }
    }

That's actually all we need to support distributed response caching with Membase. To use this, you'd need to add this to the configuration file of your service host:

  <membase>
    <servers bucket="_defaultRegion">
      <add uri="http://localhost:8091/pools/default" />
    </servers>
    <transcoder type="Agatha.Common.Caching.MembaseTranscoder, Agatha.Common" />
  </membase>

Obviously, you'd need a bucket definition for every region that you'd use and you'll probably need a different bucket URI as well ;)

You'd also need to configure Agatha to use the MembaseCacheProvider implemention:

            var config = new ServiceLayerConfiguration(Assembly.GetExecutingAssembly(), 
                typeof(HelloWorldRequest).Assembly, typeof(Agatha.Castle.Container))
                            {
                                CacheProviderImplementation = typeof(MembaseCacheProvider)
                            };
            config.Initialize();

And that's it... distributed caching of service-layer responses has never been this easy ;)

Note that i haven't committed this implementation to Agatha's Subversion repository... the plan is to add it in the 2.0 version, which will have many more changes (more on that in a future post). But if you need it already, or you need inspiration for an implementation that targets a different distributed caching server, the information in this post should get you going.

  • Andy Pook

    Question: How are you dealing with lifestyle? It would seem that once cached the object will stay there forever.

    • http://davybrion.com Davy Brion

      no, you define the expiration timeout like this:

      [EnableServiceResponseCaching(Minutes = 10)]

      you can also specify it in hours, seconds or just pass in your own timespan… that timespan is passed to the cache when it’s stored as you can see here:

      public void Store(Request request, Response response, TimeSpan expiration)
      {
      membaseClient.Store(StoreMode.Set, GetKey(request), response, expiration);
      }

      • http://www.google.com/profiles/andy.pook Andy Pook

        OK. The Membase site advertises the product as a KV database. There’s very little that talks about expiration. I’d missed that it faithfully reproduces the Memcache API and therefore the expiration parts.
        This certainly looks like a nice bit of kit. With persistence and replication it would work better in my use cases. It’s also sooo much easier to set-up and use than AppFabric Cache.

        Do you have any comment on the caching element AppFabric for Windos?

        • http://davybrion.com Davy Brion

          only played around with it for a few minutes… thought it seemed a bit cumbersome, so i asked around on twitter and someone suggested membase :)

  • http://pulse.yahoo.com/_4NV6IP22K77L2VJZAZAXRMLKSY Choclate

    @ Pook: I’ve used another distributed cache provider,NCacheand it offers two types of expiration methods, 1) Absolute: it is time based where you fixed the lifetime of data 2) Sliding: if a session remains idle for a specific time (pre-defined) it will automatically expired.

  • Reply2nelson

    Have you been able to make this work with Mono?

    • http://davybrion.com Davy Brion

      haven’t tried agatha on mono in a while since my previous attempts at getting the hello world example working weren’t successful