Multilevel Caching using Bolt.Cache – InMemory and Redis

Caching is a very important part of scaling your website. In this blog post I am trying to show how you can implement a two level caching. One Redis for distributed caching and the other one is inmemory caching. So the idea is first you look whether data exists in memory. If not then check whether the data exists in Redis. If not then load from your Sql Data source. And then save this in memory and Redis. So for next request we can serve the data from memory, if not from Redis.

The library we gonna use here is “Bolt.Cache” and “Bolt.Cache.Redis”. If you just want to use in memory caching then you just need “Bolt.Cache”. The library allows you to use more than one cache provider and populate them whenever cache missed. You can use this library to just single cache provider e.g in memory or Redis. You can even write you own CacheProvider (e.g for MemCache) and use that.

Setup 1st Level InMemory Cache

Lets see the data that we will put in cache. Here is the sample method that loads book categories from SQL. We want to cache this both in memory and Redis.


public class BooksService : IBooksService
{
	private  IBooksRepository _repository;

	public BooksService(IBooksRepository  repository)
	{
		_repository = repository;
	}	

	public IEnumerable<Category> GetCategories()
	{
		return _repository.GetCategories();
	}
}

First add in memory caching here. Download “Bolt.Cache” nuget package and Register ICacheStore in your IOC container as below:

public class CacheModule : NinjectModule
{
    public override void Load()
    {
    	// If you don't want to trace what happen behind the scene
        Bind<ILogger>().To<NullLogger>().InSingletonScope();

    	// Otherwise download nuget package "Bolt.Logger.Nlog" and register logger as below
        // Bind<ILogger>().ToMethod(x => Bolt.Logger.NLog.LoggerFactory.Create(x.Request.Target.GetType())).InSingletonScope();

        Bind<ICacheSettingsProvider>()
            .ToMethod(x => new ConfigBasedCacheSettingsProvider("Bolt.Cache.Settings"))
            .InSingletonScope();
        Bind<ICacheProvider>().To<InMemoryCacheProvider>()
            .WithConstructorArgument("name", "InMemory")
            .WithConstructorArgument("order", 0);
        
        Bind<ICacheStore>().To<CacheStore>().InSingletonScope();
    }
}

If that sounds too much there’s an easy setup available.

public class CacheModule : NinjectModule
{
    public override void Load()
    {
        Bind<ICacheStore>().ToMethod(x => CacheStoreBuilder.New().Build()).InSingletonScope();
    }
}

Setup your web.config so that you can setup cache duration and enable/disable cache based on profile.

<configuration>
  <configSections>
    <section name="Bolt.Cache.Settings" type="Bolt.Cache.Configs.CacheSettingsSection,Bolt.Cache"/>
  </configSections>
  <Bolt.Cache.Settings Disabled="False" DefaultCacheDurationInSeconds="300">
    <Profiles>
      <Profile Name="InMemory.Short" DurationInSeconds="5" Disabled="False"/>
      <Profile Name="InMemory.Medium" DurationInSeconds="1800" />
    </Profiles>
  </Bolt.Cache.Settings>
</configuration>

Now update the booksservice class with caching as below:

public class BooksService : IBooksService
{
	private IBooksRepository _repository;
	private ICacheStore _cacheStore;
	private const string CacheKeyGetAllCategories = "BooksService.GetCategories"

	public BooksService(IBooksRepository  repository, ICacheStore cacheStore)
	{
		_repository = repository;
		_cacheStore = cacheStore;
	}	

	public IEnumerable<Category> GetCategories()
	{
		return _cacheStore
					.Profile("Medium") // This will match profile settings [InMemory.Medium] profile from config. Format is <ProviderName>.<ProfileName>
					.Fetch(() => _repository.GetCategories())
					.Get(CacheKeyGetAllCategories);
	}
}

Done. The categories are now cached based on profile “Medium”. That means the settings for “InMemory.Medium” will apply to cache this. The pattern that match the profile is [<ProviderName>.<ProfileName>]. Based on our sample settings categories will be cached for 1800 seconds.

All my caching needs are now easily configurable from settings. I can even disable any caching if required. If you set a profile that does not exists in configuration section then the default value mentioned in root section (Bolt.Cache.Settings Disabled=”False” DefaultCacheDurationInSeconds=”300″) will be use.

How can I setup my second level caching using Redis

Now to setup 2nd level cache using Redis you don’t need to change any code in BooksService. First add nuget package “Bolt.Cache.Redis” and register the RedisCacheProvider in your IOC cotnainer as below. Bolt.Cache.Redis need an implementation ISerializer. You can write your own using Json.Net or add nuget package “Bolt.Serializer.Json”.

public class CacheModule : NinjectModule
{
    public override void Load()
    {
        Bind<ILogger>().ToMethod(x => Bolt.Logger.NLog.LoggerFactory.Create(x.Request.Target.GetType()));
        Bind<ICacheSettingsProvider>()
            .ToMethod(x => new ConfigBasedCacheSettingsProvider("Bolt.Cache.Settings"))
            .InSingletonScope();
        Bind<ICacheProvider>().To<InMemoryCacheProvider>()
            .WithConstructorArgument("name", "InMemory")
            .WithConstructorArgument("order", 0);

        Bind<Bolt.Cache.Redis.IConnectionSettings>()
            .ToMethod(x => Bolt.Cache.Redis.Configs.ConnectionSettingsSection
                            .Instance("Bolt.Cache.Redis.Settings"))
            .InSingletonScope();
        Bind<Bolt.Serializer.ISerializer>().To<Bolt.Serializer.Json.JsonSerializer>().InSingletonScope();
        Bind<Bolt.Cache.Redis.IConnectionFactory>().To<Bolt.Cache.Redis.ConnectionFactory>().InSingletonScope();

        Bind<ICacheProvider>().To<Bolt.Cache.Redis.RedisCacheProvider>()
            .WithConstructorArgument("name", "Redis")
            .WithConstructorArgument("order", 1);    
        
        Bind<ICacheStore>().To<CacheStore>().InSingletonScope();
    }
}

Alternative IOC setup.

public class CacheModule : NinjectModule
{
    public override void Load()
    {
        Bind<ISerializer>().To<JsonSerializer>().InSingletonScope();

        Bind<ICacheProvider>().ToMethod(x => InMemoryCacheProvider.Default).InSingletonScope();

        Bind<ICacheProvider>().ToMethod(x => RedisCacheProviderBuilder
											.New()
											.Serializer(x.Kernel.Get<ISerializer>())
											.Build())
								.InSingletonScope();

        Bind<ICacheStore>().ToMethod(x => CacheStoreBuilder
        								.New()
        								.CacheProviders(x.Kernel.GetAll<ICacheProvider>())
        								.Build())
							.InSingletonScope();
    }
}

Now add Redis connection settings in web.config as below.

<configuration>
  <configSections>
    <section name="Bolt.Cache.Settings" type="Bolt.Cache.Configs.CacheSettingsSection,Bolt.Cache"/>
    <section name="Bolt.Cache.Redis.Settings" type="Bolt.Cache.Redis.Configs.ConnectionSettingsSection,Bolt.Cache.Redis"/>
  </configSections>
  <connectionStrings>
    <add name="Redis" connectionString="localhost"/>
  </connectionStrings>
  <Bolt.Cache.Redis.Settings ConnectionStringOrName="Redis" Database="0"/>
  <Bolt.Cache.Settings Disabled="False" DefaultCacheDurationInSeconds="300">
    <Profiles>
      <Profile Name="InMemory.Short" DurationInSeconds="5" Disabled="False"/>
      <Profile Name="InMemory.Medium" DurationInSeconds="3600" Disabled="False"/>
      <Profile Name="Redis.Short" DurationInSeconds="3600" Disabled="False"/>
      <Profile Name="Redis.Medium" DurationInSeconds="86400" Disabled="False"/>
    </Profiles>
  </Bolt.Cache.Settings>
</configuration>

Other than adding redis connection string settings we also two new profiles for redis “Redis.Short” and “Redis.Medium” to tell duration of caching in redis for specific profile. Regarding redis connectionstring please check details here. Bolt.Cache.Redis using StackExchange.Redis nuget package to read/write/connect with redis.

Thats all. You don’t need to change any code in books service. The category will be cache inmemory for 3600 seconds and redis for 24 hours.

You can easily add any other caching (e.g. MemCache) just by implementing ICacheProvider.

Hope you find this library useful. Happy coding 🙂

Advertisements

5 thoughts on “Multilevel Caching using Bolt.Cache – InMemory and Redis

  1. Great work mruhul! I’ve worked through the Cache example but I am stuck on the Redis configuration. I can’t seem to find the JsonSerializer you bind to ISerializer. Looking at the source code, it expects to find it in Bolt.Serializer.Json. That namespace doesn’t seem to exist. I have Bolt.Cache, *.Cache.Redis, *.Logger, *.Serializer, and *.Serializer.Json all installed as packages. Still cannot find it. Am I missing something?

    • Hi Shane,
      Good to know you like this library. Just to be clear JsonSerializer is not part of Bolt.Serializer. You need to add nuget package Bolt.Serializer.Json in your application. This package contains JsonSerializer class. Another option is you can implement a class for ISeializer by yourself just by using Json.Net library. Hope this answers your question. If you still having problem let me know.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s