Introducing Purse

Purse Logo

What is Purse?

Purse is a simple thread-safe, in-proc, in-memory cache for .NET.  Its main purpose is to replace the ad-hoc dictionaries you would use in your code to cache simple key/value pairs. Purse tries to provide most of the features you would want from an in-proc cache while providing a blazing fast yet simple experience.

What is Purse really good at?

Purse is really good at storing small data-sets like reference data (eg. list of countries, permission profiles etc.). These kinds of data are generally small and can fit in memory without increasing the memory footprint drastically. They are also accessed frequently. Choosing the right data to put in your purse could have a great impact on your application’s performance.

Another interesting usage is using Purse as a throttle function. Using Purser’s read-through and TTL functionality you can implement a pretty simple throttle function in your application to limit number of calls made to a specific function.

 

What Purse isn’t

Purse by default uses a .NET ConcurrentDictionary. This allows it to be extremely efficient but at the same time it also makes Purse not the best candidate for large data-sets. That’s why it’s called Purse and not Warehouse or Truck or some other big storage metaphors ;). Keep in mind Purse doesn’t have background evacuation so whatever is put in will stay in until it has been manually removed or replaced. Also the TTL is evaluated during a cache hit so expired items will stay in cache until they are replaced with an updated value.

 

Read-Through

This method exposes 90% of functionality available in Purse. It allows you to populate and read Purse in one line.
When reading a value from Purse using the Get method you provide a lambda that should be used in case the key doesn’t exist in cache or has expired. If that happens Purse will automatically call your method and store the returned value in cache for the given key and return the result. This means you never have to worry about cache misses or synchronization again. The cache will automatically populate itself over time.

The last parameter is an optional TimeSpan that allows you to specify how long should the item be considered valid.  Keep in mind that expiry is calculated at insertion and there is no automatic evacuation of expired items.  This means there is no impact on cache performance when specifying a TTL for an item. Expired items will stay in cache until they are asked for in which case the callback method is called and stored value will be refreshed with a new value.

 

Changing the backing storage

By default a ConcurrentDictionary is used as the backing store but you can implement your own ICacheStorage and pass it to the cache constructor. This way you can technically have Purse backed by Redis, Mongo, MemCache etc. That might be my next small project. Purse.Redis?

 

Full Sample

Here we are using Purse on top of some imaginary config store to cache reading of config values.

public class ConfigSample
{
    private readonly Cache<string, string> _configCache;

    public ConfigSample()
    {
        //Create a new instance of our cache that uses string for both key and value.
        _configCache = new Cache<string, string>();
    }

    public string Get(string key)
    {
        //Tries to get the request value from cache, if it doesn't exist
        //call the function and retrieve the value. (this is generally called a read-through pattern)
        return _configCache.Get(key, () => ReadValue(key));
    }

    public void Set(string key, string value)
    {
        _configCache.Remove(key);     //remove the existing value from cache
        WriteValue(key, value);       //write the new value to the backing store
        _configCache.Set(key, value); //update the cache with the new value
    }

    private string ReadValue(string key)
    {
        //slow call to read config value from config store.
        throw new NotImplementedException();
    }

    private void WriteValue(string key, string value)
    {
        //call to write config value to config store.
        throw new NotImplementedException();
    }
}


GitHub: https://github.com/kayone/Purse
Nuget: http://www.nuget.org/packages/Purse/

I would also like to thank Yulia Yemelianova for donating the beautiful logo.

Twitter1Facebook0Google+0LinkedIn0Pinterest0