PommaLabs.KVLite.Polly
12.4.0
Prefix Reserved
dotnet add package PommaLabs.KVLite.Polly --version 12.4.0
NuGet\Install-Package PommaLabs.KVLite.Polly -Version 12.4.0
<PackageReference Include="PommaLabs.KVLite.Polly" Version="12.4.0" />
paket add PommaLabs.KVLite.Polly --version 12.4.0
#r "nuget: PommaLabs.KVLite.Polly, 12.4.0"
// Install PommaLabs.KVLite.Polly as a Cake Addin #addin nuget:?package=PommaLabs.KVLite.Polly&version=12.4.0 // Install PommaLabs.KVLite.Polly as a Cake Tool #tool nuget:?package=PommaLabs.KVLite.Polly&version=12.4.0
KVLite
KVLite is a partition-based key-value cache built for SQL RDBMSs.
Library is feature complete and no further development is planned on this project, except for routine maintenance and bug fixes.
KVLite entries can be stored either in persistent or volatile fashion, and each key/value pair can have its own lifetime and refresh mode.
Entries are grouped by partition, where each key must be unique only inside the partition it belongs to.
Following RDBMSs are currently supported by KVLite:
- MySQL and MariaDB (.NET and .NET Core, with integration tests for MySQL only)
- PostgreSQL (.NET and .NET Core, with integration tests)
- SQL Server (.NET and .NET Core, with integration tests)
- SQLite (.NET and .NET Core, with integrations tests)
KVLite implements different caching adapters for various libraries and frameworks:
An in-memory driver is also provided for unit testing.
Table of Contents
Install
KVLite requires a driver for your RDBMS, packages are linked above. For example, if you use MySQL, the you should install:
dotnet add package PommaLabs.KVLite.MySql
After that, you could install specific adapters (like the one for ASP.NET Core) and you should set your DB up according to the documentation below.
Storage layout
KVLite stores cache entries in a dedicated table, whose schema is as much tuned as possible for each RDBMS. The logical schema for cache entries table is the following:
Column name | Data type | Content |
---|---|---|
kvle_id |
guid or long |
Automatically generated ID. This is the primary key. |
kvle_hash |
long |
Hash of partition and key. This is the unique key. |
kvle_expiry |
long |
When the entry will expire, expressed as seconds after UNIX epoch. |
kvle_interval |
long |
How many seconds should be used to extend expiry time when the entry is retrieved. |
kvle_value |
byte[] |
Serialized and optionally compressed content of this entry. |
kvle_compressed |
bool |
Whether the entry content was compressed or not. |
kvle_partition |
string |
A partition holds a group of related keys. |
kvle_key |
string |
A key uniquely identifies an entry inside a partition. |
kvle_creation |
long |
When the entry was created, expressed as seconds after UNIX epoch. |
kvle_parent_hash0 |
long |
Optional parent entry hash, used to link entries in a hierarchical way. |
kvle_parent_key0 |
string |
Optional parent entry key, used to link entries in a hierarchical way. |
kvle_parent_hash1 |
long |
Optional parent entry hash, used to link entries in a hierarchical way. |
kvle_parent_key1 |
string |
Optional parent entry key, used to link entries in a hierarchical way. |
kvle_parent_hash2 |
long |
Optional parent entry hash, used to link entries in a hierarchical way. |
kvle_parent_key2 |
string |
Optional parent entry key, used to link entries in a hierarchical way. |
If SQL user chosen for KVLite has enough privileges, then cache entries table will be automatically created. Anyway, specialized schemas for supported RDBMS systems are available inside this project repository, at following links:
Each script might have a few comments suggesting how to further optimize cache entries table storage depending on the actual version of the specific RDBMS system.
Customizing SQL layout
Default name for cache entries table is kvle_cache_entries
and default SQL schema is kvlite
.
However, those values can be easily changed at runtime, as we do in the following snippet:
// Change cache entries table name for PostgreSQL cache.
PostgreSqlCache.DefaultInstance.Settings.CacheEntriesTableName = "my_custom_name";
// Change SQL schema name for MySQL cache.
MySqlCache.DefaultInstance.Settings.CacheSchemaName = "my_schema_name";
// Change both table ans schema name for SQL Server cache.
SqlServerCache.DefaultInstance.Settings.CacheEntriesTableName = "my_custom_name";
SqlServerCache.DefaultInstance.Settings.CacheSchemaName = "my_schema_name";
Please perform those customizations as early as your application starts; for example, these are good places where to put the lines:
Program.cs
for console and Windows applications.Global.asax.cs
for classic web applications.Startup.cs
for Owin-based web applications or ASP.NET Core.
Usage
Let's start with a simple example of what you can do with KVLite:
/// <summary>
/// Learn how to use KVLite by examples.
/// </summary>
internal static class Program
{
/// <summary>
/// Learn how to use KVLite by examples.
/// </summary>
public static void Main()
{
// Some variables used in the examples.
var examplePartition1 = "example partition 1";
var examplePartition2 = "example partition 2";
var exampleKey1 = "example key 1";
var exampleKey2 = "example key 2";
var simpleValue = Math.PI;
var complexValue = new ComplexValue
{
Integer = 21,
NullableBoolean = null,
String = "Learning KVLite",
Dictionary = new Dictionary<short, ComplexValue>
{
[1] = new() { NullableBoolean = true },
[2] = new() { String = "Nested..." }
}
};
/*
* KVLite stores its values inside a given partition and each value is linked to a key.
* KVLite can contain more than one partition and each partition can contain more than one key.
*
* Therefore, values are stored according to this logical layout:
*
* [partition1] --> key1/value1
* --> key2/value2
* [partition2] --> key1/value1
* --> key2/value2
* --> key3/value3
*
* A key is unique inside a partition, not inside all cache.
* A partition, instead, is unique inside all cache.
*/
// You can start using the default caches immediately. Let's try to store some values in
// a way similar to the figure above, using the default persistent cache.
var persistentCache = PersistentCache.DefaultInstance;
persistentCache.AddTimed(examplePartition1, exampleKey1, simpleValue, persistentCache.Clock.UtcNow + TimeSpan.FromMinutes(5));
persistentCache.AddTimed(examplePartition1, exampleKey2, simpleValue, persistentCache.Clock.UtcNow + TimeSpan.FromMinutes(10));
persistentCache.AddTimed(examplePartition2, exampleKey1, complexValue, persistentCache.Clock.UtcNow + TimeSpan.FromMinutes(10));
persistentCache.AddTimed(examplePartition2, exampleKey2, complexValue, persistentCache.Clock.UtcNow + TimeSpan.FromMinutes(5));
PrettyPrint(persistentCache);
// Otherwise, you can customize you own cache... Let's see how we can use a volatile
// cache. Let's define the settings that we will use in new volatile caches.
var volatileCacheSettings = new VolatileCacheSettings
{
CacheName = "My In-Memory Cache", // The backend.
};
// Then the settings that we will use in new persistent caches.
var persistentCacheSettings = new PersistentCacheSettings
{
CacheFile = "CustomCache.sqlite", // The SQLite DB used as the backend for the cache.
ChancesOfAutoCleanup = 0.5, // Chance of an automatic a cache cleanup being issued.
};
// We create both a volatile and a persistent cache.
var volatileCache = new VolatileCache(volatileCacheSettings);
persistentCache = new PersistentCache(persistentCacheSettings);
// Use the new volatile cache!
volatileCache.AddTimed(examplePartition1, exampleKey1, Tuple.Create("Volatile!", 123), TimeSpan.FromMinutes(60));
PrettyPrint(volatileCache);
// Use the new persistent cache!
persistentCache.AddTimed(examplePartition2, exampleKey2, Tuple.Create("Persistent!", 123), TimeSpan.FromMinutes(60));
PrettyPrint(persistentCache);
/*
* An entry can be added to the cache in three different ways.
*
* "Timed" values last until the specified date and time, or for a specified timespan.
* Reading them will not extend their lifetime.
*
* "Sliding" values last for the specified lifetime, but, if read,
* their lifetime will be extended by the timespan specified initially.
*/
// Let's clear the volatile cache and let's a value for each type.
volatileCache.Clear();
volatileCache.AddTimed(examplePartition1, exampleKey1, simpleValue, volatileCache.Clock.UtcNow + TimeSpan.FromMinutes(10));
volatileCache.AddTimed(examplePartition1, exampleKey2, complexValue, TimeSpan.FromMinutes(15));
volatileCache.AddSliding(examplePartition2, exampleKey2, complexValue, TimeSpan.FromMinutes(15));
PrettyPrint(volatileCache);
}
private static void PrettyPrint(ICache cache)
{
Console.WriteLine($"Printing the contents of a {cache.GetType().Name}");
// When we use "Peek*" methods, the expiration time of entries is left untouched.
var cacheEntries = cache.PeekEntries<object>();
foreach (var cacheEntry in cacheEntries.OrderBy(ci => ci.Partition).ThenBy(ci => ci.Key))
{
Console.WriteLine($"{cacheEntry.Partition} --> {cacheEntry.Key} --> {cacheEntry.Value}");
}
Console.WriteLine();
}
private sealed class ComplexValue
{
public int Integer { get; set; }
public bool? NullableBoolean { get; set; }
public string String { get; set; }
public IDictionary<short, ComplexValue> Dictionary { get; set; }
public override string ToString() => nameof(ComplexValue);
}
}
Polly cache provider
Moreover, KVLite can be used as Polly cache provider:
// Every KVLite cache can be interfaced with Polly: Memory, MySQL, PostgreSQL, ...
var options = new KVLiteCacheProviderOptions();
var cacheProvider = new KVLiteSyncCacheProvider<string>(PersistentCache.DefaultInstance, options);
var cachePolicy = Policy.Cache(cacheProvider, TimeSpan.FromMinutes(10));
var myGuid1 = cachePolicy.Execute(() => Guid.NewGuid().ToString(), new Context("MyGuid"));
var myGuid2 = cachePolicy.Execute(() => Guid.NewGuid().ToString(), new Context("MyGuid"));
// Two GUIDs are equal because they share the same key.
Debug.Assert(myGuid1 == myGuid2);
myGuid1 = cachePolicy.Execute(() => Guid.NewGuid().ToString(), new KVLiteContext("My", "Complex", "Key", 1));
myGuid2 = cachePolicy.Execute(() => Guid.NewGuid().ToString(), new KVLiteContext("My", "Complex", "Key", 2));
// Two GUIDs are not equal because they do not share the same key.
Debug.Assert(myGuid1 != myGuid2);
Examples
Further examples can be found in the following projects:
- ASP.NET Core: It shows how to register KVLite services and how to use it as a proper distributed cache implementation. Moreover, it shows how to use KVLite session extensions.
Maintainers
Contributing
MRs accepted.
Small note: If editing the README, please conform to the standard-readme specification.
Editing
Visual Studio Code, with Remote Containers extension, is the recommended way to work on this project.
A development container has been configured with all required tools.
Visual Studio Community is also supported
and an updated solution file, kvlite.sln
, has been provided.
Restoring dependencies
When opening the development container, dependencies should be automatically restored.
Anyway, dependencies can be restored with following command:
dotnet restore
Running tests
Tests can be run with following command:
dotnet test
Tests can also be run with following command, which collects coverage information:
./build.sh --target run-tests
License
MIT © 2014-2024 PommaLabs Team and Contributors
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. 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. net9.0 is compatible. |
-
net8.0
- Polly (>= 8.5.0)
- PommaLabs.KVLite (>= 12.4.0)
-
net9.0
- Polly (>= 8.5.0)
- PommaLabs.KVLite (>= 12.4.0)
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 |
---|---|---|
12.4.0 | 55 | 12/11/2024 |
12.3.0 | 190 | 11/15/2023 |
12.2.1 | 162 | 12/18/2022 |
12.2.0 | 97 | 12/17/2022 |
12.1.0 | 109 | 12/16/2022 |
12.0.0 | 198 | 1/23/2022 |
11.1.0 | 1,703 | 11/28/2021 |
11.0.7 | 157 | 9/24/2021 |
11.0.5 | 462 | 4/25/2021 |
11.0.4 | 405 | 3/17/2021 |
11.0.3 | 446 | 3/14/2021 |
11.0.2 | 435 | 1/16/2021 |
11.0.1 | 438 | 12/27/2020 |
11.0.0 | 472 | 11/1/2020 |
10.1.1 | 574 | 10/26/2020 |
10.1.0 | 558 | 8/12/2020 |
10.0.5 | 515 | 6/19/2020 |
10.0.4 | 534 | 6/14/2020 |
10.0.3 | 559 | 6/3/2020 |
10.0.2 | 536 | 6/1/2020 |
10.0.0 | 589 | 3/9/2020 |
9.3.3 | 574 | 3/2/2020 |
9.2.7 | 576 | 2/7/2020 |
9.2.6 | 547 | 2/6/2020 |
9.2.5 | 584 | 2/5/2020 |
9.2.4 | 599 | 1/4/2020 |
9.2.3 | 660 | 1/2/2020 |
9.1.2 | 624 | 8/31/2019 |
9.0.6 | 617 | 5/5/2019 |
9.0.5 | 654 | 2/26/2019 |
9.0.4 | 667 | 2/25/2019 |
9.0.3 | 762 | 12/28/2018 |
8.1.3 | 924 | 8/17/2018 |
8.1.2 | 876 | 8/16/2018 |
8.1.0 | 888 | 8/14/2018 |
8.0.2 | 910 | 7/7/2018 |
8.0.1 | 980 | 6/12/2018 |
7.2.3 | 977 | 4/5/2018 |
7.2.2 | 989 | 4/1/2018 |
7.2.1 | 1,143 | 4/1/2018 |