ModernCaching 0.0.2

dotnet add package ModernCaching --version 0.0.2                
NuGet\Install-Package ModernCaching -Version 0.0.2                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ModernCaching" Version="0.0.2" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add ModernCaching --version 0.0.2                
#r "nuget: ModernCaching, 0.0.2"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install ModernCaching as a Cake Addin
#addin nuget:?package=ModernCaching&version=0.0.2

// Install ModernCaching as a Cake Tool
#tool nuget:?package=ModernCaching&version=0.0.2                

ModernCaching

A 2-layer, performant and resilient caching solution for modern .NET.

A typical cache provided by this library consists of:

  • a synchronous local cache that implements ICache
  • an asynchronous distributed cache that implements IAsyncCache (e.g. memcache, redis)
  • a data source that implements IDataSource (e.g. relational database, Web API, CPU intensive task...)

These 3 components form an IReadOnlyCache. The 2 cache layers are populated from the IDataSource with a backfilling mechanism when getting a value or by preloading some data when building the cache.

schema

ModernCaching doesn't provide implementations of IAsyncCache or IDataSource because they are usually tied to the business. Only a single implementation of ICache is built-in: MemoryCache.

This library is inspired by a component of the Criteo's SDK that handles 10B+ requests per second (hint: it's a lot). ModernCaching is production ready but lacks a way to invalidate data (#1).

Installation

ModernCaching is available on Nuget.

dotnet add package ModernCaching

Features

  • Strict API. IReadOnlyCache has only two methods:
    • TryPeek, a synchronous operation to only get the value if it's present in the local cache and refresh in the background if needed.
    • TryGetAsync, an asynchronous operation to get the first fresh value in the local cache, distributed cache or the data source, in that order.
  • Performance. Unlike other caching libraries that use a string as a key or an object as value or both, ModernCaching uses a generic key and value. That way, getting a value from the local cache doesn't require any allocation for simple type keys such as int or more complex user-defined objects. See the benchmarks.
  • Resilience. With its fixed number of layers, each behavior is clearly defined when one of these layers is down. For instance, the data source is skipped if the distributed cache is unavailable to avoid DDOSing it.
  • Instrumentation. Metrics are exposed using OpenTelemetry API. Errors from user-code are logged if a logger is specified.

Example

This example caches the user information. The first layer is implemented with an in-memory cache, the second one is a redis where we specify how to create the key and how to serialize the value using the interface IKeyValueSerializer. Behind these two layers stands the IDataSource.

var cache = await new ReadOnlyCacheBuilder<Guid, User>("user-cache", new UserDataSource("Host=localhost;User ID=postgres"))
    .WithLocalCache(new MemoryCache<Guid, User>())
    .WithDistributedCache(new RedisAsyncCache(redis), new ProtobufKeyValueSerializer<Guid, User>())
    .BuildAsync();

Guid userId = new("cb22ff11-4683-4ec3-b212-7f1d0ab378cc");
bool found = cache.TryPeek(userId, out User? user); // Only check local cache with background refresh.
(bool found, User? user) = await cache.TryGetAsync(userId); // Check all layers for a fresh value.

The rest of the code as well as other examples can be found in src/ModernCaching.ITest.

Benchmarks

Benchmark of the very hot path of different caching libraries (CacheTower, Foundatio, LazyCache, FusionCache, EasyCaching, CacheManager), that is, getting locally cached data. The .NET ConcurrentDictionary was also added as a baseline.

Method Mean Error StdDev Ratio RatioSD Allocated
ConcurrentDictionary 9.728 ns 0.1687 ns 0.1578 ns 1.00 0.00 -
ModernCaching 23.887 ns 0.1283 ns 0.1200 ns 2.46 0.04 -
CacheTower 111.146 ns 0.6491 ns 0.5754 ns 11.44 0.21 96 B
Foundatio 251.498 ns 0.2877 ns 0.2551 ns 25.88 0.43 216 B
LazyCache 258.821 ns 1.2548 ns 0.9797 ns 26.64 0.43 96 B
FusionCache 292.959 ns 1.8200 ns 1.6134 ns 30.15 0.52 184 B
EasyCaching 383.052 ns 0.2729 ns 0.2419 ns 39.42 0.65 264 B
CacheManager 465.721 ns 0.5287 ns 0.4687 ns 47.93 0.76 344 B

This library has similar performance as a raw ConcurrentDictionary since its hot path is a thin layer around it. It doesn't allocate anything, putting no pressure on the garbage collector.

Code can be found in src/ModernCaching.Benchmarks.

Instrumentation

Metrics

Metrics are exposed using .NET implementation of the OpenTelemetry Metrics API (System.Diagnostics.Metrics) under the source name ModernCaching. They can be exported using the OpenTelemetry .NET SDK.

Logs

Use WithLoggerFactory on the builder to log all user-code errors coming from IAsyncCache, IKeyValueSerializer or IDataSource.

License

All code found in this repository is licensed under MIT. See the LICENSE file in the project root for the full license text.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.0.2 6,140 11/28/2021
0.0.1 157 8/29/2021