Ivan-Mark Debono
Ivan-Mark Debono

Reputation: 16340

Is Redis a viable solution for a local cache?

In my scenario, I have a Winforms client that connects to WebApi2. The data is stored in a SQL Server database.

To speed up performance, I am researching if storing data in local cache is a viable solution. Preferably, the local cache should be stored in files instead of kept in-memory as RAM might be an issue. The data is all POCO classes, some being much more complex than others, and most classes being related to each other.

I have made a shortlist of which frameworks might be viable:

  1. MemoryCache
  2. MemCached
  3. CacheManager
  4. StackExchange.Redis
  5. Local Database

Using MemoryCache, I would need to implement my own solution, but it will fit my initial requirements.

However, one common problem that I am seeing is the updating of related classes. For example, I have a relationship between CustomerAddress and PostCode. If I change some properties in a postcode object, I can easily update its local cache. But how is it possible to update/invalidate any other classes that use this postcode, in this case CustomerAddress?

Does any of the frameworks above have methods that help in this kind of situation, or is it totally dependent on the developer to handle such cache invalidation?

Upvotes: 3

Views: 3527

Answers (2)

thepirat000
thepirat000

Reputation: 13124

The CachingFramework.Redis library provides a mechanism to relate tags to keys and hashes so you can then invalidate them in a single operation.

I'm assuming that you will:

  • Store the Customer Addresses in Redis with keys like "Address:{AddressId}".
  • Store the Post Codes in Redis with keys like "PostCode:{PostCodeId}".

And that your model is something like this:

public class CustomerAddress
{
    public int CustomerAddressId { get; set; }
    public int CustomerId { get; set; }
    public int PostCodeId { get; set; }
}
public class PostCode
{
    public int PostCodeId { get; set; }
    public string Code { get; set; }
}

My suggestion is to:

  • Mark the Customer Addresses objects on Redis with tags like "Tag-PostCode:{PostCodeId}".
  • Use a cache-aside pattern to retrieve the Customer Addresses and Post Codes from cache/database.
  • Invalidate the cache objects by tag when a Post Code is changed.

Something like this should probably work:

public class DataAccess
{
    private Context _cacheContext = new CachingFramework.Redis.Context("localhost:6379");

    private string FormatPostCodeKey(int postCodeId)
    {
        return string.Format("PostCode:{0}", postCodeId);
    }

    private string FormatPostCodeTag(int postCodeId)
    {
        return string.Format("Tag-PostCode:{0}", postCodeId);
    }

    private string FormatAddressKey(int customerAddressId)
    {
        return string.Format("Address:{0}", customerAddressId);
    }

    public void InsertPostCode(PostCode postCode)
    {
        Sql.InsertPostCode(postCode);
    }

    public void UpdatePostCode(PostCode postCode)
    {
        Sql.UpdatePostCode(postCode);
        //Invalidate cache: remove CustomerAddresses and PostCode related
        _cacheContext.Cache.InvalidateKeysByTag(FormatPostCodeTag(postCode.PostCodeId));
    }

    public void DeletePostCode(int postCodeId)
    {
        Sql.DeletePostCode(postCodeId);
        _cacheContext.Cache.InvalidateKeysByTag(FormatPostCodeTag(postCodeId));
    }

    public PostCode GetPostCode(int postCodeId)
    {
        // Get/Insert the postcode from/into Cache with key = PostCode{PostCodeId}. 
        // Mark the object with tag = Tag-PostCode:{PostCodeId}
        return _cacheContext.Cache.FetchObject(
            FormatPostCodeKey(postCodeId),              // Redis Key to use
            () => Sql.GetPostCode(postCodeId),          // Delegate to get the value from database
            new[] { FormatPostCodeTag(postCodeId) });   // Tags related
    }

    public void InsertCustomerAddress(CustomerAddress customerAddress)
    {
        Sql.InsertCustomerAddress(customerAddress);
    }

    public void UpdateCustomerAddress(CustomerAddress customerAddress)
    {
        var updated = Sql.UpdateCustomerAddress(customerAddress);
        if (updated.PostCodeId != customerAddress.PostCodeId)
        {
            var addressKey = FormatAddressKey(customerAddress.CustomerAddressId);
            _cacheContext.Cache.RenameTagForKey(addressKey, FormatPostCodeTag(customerAddress.PostCodeId), FormatPostCodeTag(updated.PostCodeId));
        }
    }

    public void DeleteCustomerAddress(CustomerAddress customerAddress)
    {
        Sql.DeleteCustomerAddress(customerAddress.CustomerAddressId);
        //Clean-up, remove the postcode tag from the CustomerAddress:
        _cacheContext.Cache.RemoveTagsFromKey(FormatAddressKey(customerAddress.CustomerAddressId), new [] { FormatPostCodeTag(customerAddress.PostCodeId) });
    }

    public CustomerAddress GetCustomerAddress(int customerAddressId)
    {
        // Get/Insert the address from/into Cache with key = Address:{CustomerAddressId}. 
        // Mark the object with tag = Tag-PostCode:{PostCodeId}
        return _cacheContext.Cache.FetchObject(
            FormatAddressKey(customerAddressId),
            () => Sql.GetCustomerAddress(customerAddressId),
            a => new[] { FormatPostCodeTag(a.PostCodeId) });
    }
}

Upvotes: 1

Orel Eraki
Orel Eraki

Reputation: 12196

To speed up performance, I am researching if storing data in local cache is a viable solution. Preferably, the local cache should be stored in files instead of kept in-memory as RAM might be an issue

The whole issue is to avoid storing it in files, to avoid DISK operations which are slow, thus Redis is RAM based memory.

Does any of the frameworks above have methods that help in this kind of situation, or is it totally dependent on the developer to handle such cache invalidation?

You can save the entire object as JSON instead of applying logic and disassembles the objects, which will be also slow and error prone when applying changes.

Upvotes: 1

Related Questions