In-Memory "L1" cache for the .NET StackExchange.Redis library.
This library implements the StackExchange.Redis.IDatabase
interface to provide an in-memory cache layer between your .NET application and Redis.
Additionally, it can be used as a purely in-memory cache, without any Redis instance. This is useful for simulating redis use, in test cases, for example.
Network latency is your primary bottleneck when talking to Redis. Usually this is solved via the use of in-memory caching on the application server; this project is an attempt to generalise that. Any data sent to Redis is intercepted and stored in memory using the .NET MemoryCache, and later requests for that data are returned from MemoryCache if possible.
If you have multiple clients all talking to Redis, then each one can still use this cache layer. This is achieved by invalidating data appropriately in the background by using a combination of Redis keyspace notifications and custom pub/sub channels. Due to the redis at-most-once delivery of messages, if a message is lost, then the in-memory cache will not be updated. This is a risk which needs to be managed.
If you are already using StackExchange.Redis, then integration into your project is a 2-line change:
IDatabase database = connection.GetDatabase(); //Get your Redis IDatabase instance as normal
IDatabase l1Database = new StackRedis.L1.RedisL1Database(database) //Create the in-memory cached database on top
Since the RedisL1Database
implements IDatabase
, it's a simple swap.
If you wish to clear your in-memory state, simply Dispose
it and re-create it.
If you use this library for one Redis client, you must use it for all clients of that database. This is because Redis keyspace notifications alone are not enough to accelerate the Hash and Set types; custom messages are required. If you use this library to accelerate only one client, then that client risks holding on to stale data keys expire or are deleted.
No doubt there are other limitations I haven't thought of, so all feedback is of course welcome.
Any types/functions that are not accelerated will work as normal, as the request will be passed directly to Redis. Details on what is accelerated is laid out below.
String
The String
type is fully accelerated, using MemoryCache
for storage.
Hash
The Hash
type is fully accelerated with a Dictionary
within MemoryCache
used for storage.
Set
The Set
type is heavily accelerated using a HashSet
within MemoryCache
used for in-memory storage.
SortedSet
SortedSet
operations involving score
are currently accelerated. This is done using the concept of 'disjointed sets' in memory - a collection of sorted subsets of the full sorted set. It would be possible to use the same technique to accelerate calls involving Rank, but this isn't implemented. Use SortedSetRangeByScoreWithScores
for best caching performance. Note however that specifying 'skip' or 'take' prevents results being cached.
List
The List
type is not accelerated, as it cannot be cached meaningfully in memory. This is because operations generally involve the head or the tail of the list, and we cannot know whether or not we have the head or tail in memory. Most operations would involve invalidating the entire list data and so there would be little benefit. All List
operations are passed through directly to Redis.