In my last post I talked about switching IDistributedCache out for IMemoryCache, since I'm running on a single server the in-memory caching is good enough for me. Though IDistributedCache also provides an in-memory caching implementation, I have to pay for its overhead with serialization and de-serialization of objects I cache.
Unfortunately, IMemoryCache has its concerns. It has the following method GetOrCreate<T> which is often used to retrieve or create objects to the cache, but this method is not thread safe.
GetOrCreate<TItem>(IMemoryCache, Object, Func<ICacheEntry,TItem>);
Notice the method has a delegate
Func<ICahceEntry, TItem>, when the website is under high traffic, multiple requests could end up calling the delegate concurrently. There is an open issue on GitHub for this specific issue GetOrCreate is not atomic which is a serious issue for a cache ( eg poor perf and memory issues).
I searched around and found Scott Hanselman wrote a post with a potential alternative solution, Using LazyCache for clean and simple .NET Core in-memory caching.
So I looked into LazyCache and it's specially created to solve this problem. Internally, it's using IMemoryCache but it has the added benefit that it
guarantees to only execute your cachable delegates once (it's lazy!). Under the hood it leverages Microsoft.Extensions.Caching and Lazy to provide performance and reliability in heavy load scenarios.
So I tweaked my caching again and here is an updated class diagram. The MemCache implementation is now using LazyCache in place of IMemoryCache. The IAppCache is LazyCache's own interface to inject into my MemCache implementation.
LazyCache also comes with unit testing in mind, it provides a mocked caching service called MockCachingService that helps me mock it in my tests. The MockCachingService is basically a mock implementation of the IAppCache that does not do any caching.
var cache = new MemCache(new MockCachingService());
There is only one place where I want to test IMemoryCache directly to let the users get a sense of how caching works that I created my own alternative to this mock, I named it MockMemCacheService.