RepletoryLib.Caching.Redis8
1.0.0
dotnet add package RepletoryLib.Caching.Redis8 --version 1.0.0
NuGet\Install-Package RepletoryLib.Caching.Redis8 -Version 1.0.0
<PackageReference Include="RepletoryLib.Caching.Redis8" Version="1.0.0" />
<PackageVersion Include="RepletoryLib.Caching.Redis8" Version="1.0.0" />
<PackageReference Include="RepletoryLib.Caching.Redis8" />
paket add RepletoryLib.Caching.Redis8 --version 1.0.0
#r "nuget: RepletoryLib.Caching.Redis8, 1.0.0"
#:package RepletoryLib.Caching.Redis8@1.0.0
#addin nuget:?package=RepletoryLib.Caching.Redis8&version=1.0.0
#tool nuget:?package=RepletoryLib.Caching.Redis8&version=1.0.0
RepletoryLib.Caching.Redis8
Redis 8 cache provider for RepletoryLib. Implements ICacheService and IDistributedLockService from RepletoryLib.Caching.Abstractions and adds first-class wrappers for the new data types and protocol features introduced in Redis 8.
Part of the RepletoryLib ecosystem -- standalone, reusable .NET 10 libraries with zero business logic.
When to choose Redis 8 over Caching.Redis
This package is a separate, opt-in provider -- both Redis 7 (RepletoryLib.Caching.Redis) and Redis 8 (RepletoryLib.Caching.Redis8) implement the same abstractions. Pick whichever matches the Redis server version your environment targets:
Use Caching.Redis (Redis 7) when… |
Use Caching.Redis8 when… |
|---|---|
| You target a Redis 7.x server | You target a Redis 8.x server |
| You only need basic string-cache + RedLock | You want partial JSON updates without serialise-round-trip |
| Cluster nodes are mixed-version | You want per-field hash TTL (HEXPIRE) |
| You want native vector similarity for AI / semantic search | |
You want server-assisted client-side caching (RESP3 CLIENT TRACKING) |
|
| You want built-in probabilistic structures (Bloom, Top-K) without modules |
The two providers can coexist in a solution -- register only one of them in a given service.
What's new compared to Caching.Redis
| Feature | Backed by | Interface |
|---|---|---|
Native JSON cache -- store entities as JSON.SET documents, query/patch with JSONPath, JSON.MERGE, JSON.NUMINCRBY |
Redis 8 native JSON | IRedisJsonCacheService, and transparently inside ICacheService when UseNativeJson = true |
Hash field TTL -- per-field expiration on hashes (HEXPIRE / HTTL / HPERSIST) |
Redis 8 (added in 7.4, default in 8) | IHashFieldCacheService |
| Bloom filter -- probabilistic membership for stampede prevention, idempotency, dedupe | Redis 8 built-in BF.* |
IBloomFilterService |
| Top-K sketch -- hot-key detection and trending analytics | Redis 8 built-in TOPK.* |
ITopKService |
| Vector sets -- native vector similarity search inside Redis | Redis 8 VADD / VSIM / VEMB |
IVectorCacheService |
| RESP3 + server push -- typed replies and lower parse overhead | Redis 8 / StackExchange.Redis 2.8 | enabled by default |
| Server-assisted client tracking -- the server pushes key invalidations to subscribed clients | CLIENT TRACKING BCAST |
EnableClientTracking = true |
Non-blocking bulk eviction -- UNLINK over DEL for RemoveByPrefixAsync |
Redis 6+, on by default in this package | -- |
Installation
dotnet add package RepletoryLib.Caching.Redis8
<PackageReference Include="RepletoryLib.Caching.Redis8" Version="1.0.0" />
Dependencies
| Package | Purpose |
|---|---|
RepletoryLib.Caching.Abstractions |
ICacheService, IDistributedLockService contracts |
StackExchange.Redis 2.8.22 |
RESP3 client |
NRedisStack 0.13.1 |
High-level wrappers for JSON / Bloom / Top-K |
RedLock.net 2.3.2 |
Distributed lock |
Prerequisites
- Redis 8+ running and accessible.
Update your docker-compose.yml to use the Redis 8 image:
redis:
image: redis:8-alpine # bundles JSON, Search, Bloom, Vector
container_name: repletorylib-redis
ports:
- "6379:6379"
Quick start
using RepletoryLib.Caching.Redis8;
builder.Services.AddRepletoryRedis8(builder.Configuration);
{
"RepletoryRedis8": {
"ConnectionString": "localhost:6379",
"InstanceName": "myapp",
"DefaultExpiryMinutes": 60,
"UseResp3": true,
"UseNativeJson": true,
"EnableClientTracking": false,
"ClientTrackingPrefixes": [],
"BloomFilterDefaultCapacity": 100000,
"BloomFilterDefaultErrorRate": 0.01,
"UseUnlinkForBulkDelete": true,
"RetryCount": 3
}
}
AddRepletoryRedis8 registers:
| Service | Implementation |
|---|---|
ICacheService |
Redis8CacheService |
IDistributedLockService |
Redis8DistributedLockService |
IRedisJsonCacheService |
RedisJsonCacheService |
IHashFieldCacheService |
HashFieldCacheService |
IBloomFilterService |
BloomFilterService |
ITopKService |
TopKService |
IVectorCacheService |
VectorCacheService |
Configuration
Redis8Options -- section RepletoryRedis8
| Property | Type | Default | Description |
|---|---|---|---|
ConnectionString |
string |
"localhost:6379" |
Redis server connection string |
InstanceName |
string |
"" |
Prefix for all keys (namespace isolation) |
DefaultExpiryMinutes |
int |
60 |
Default TTL for cache entries |
EnableCompression |
bool |
false |
GZip-compress opaque values. Ignored when UseNativeJson = true |
RetryCount |
int |
3 |
Connection retry attempts |
UseResp3 |
bool |
true |
Negotiate RESP3 protocol |
UseNativeJson |
bool |
true |
ICacheService stores non-primitive values via JSON.SET |
EnableClientTracking |
bool |
false |
Enable server-assisted invalidation push (CLIENT TRACKING BCAST) |
ClientTrackingPrefixes |
List<string> |
[] |
Prefixes the server tracks (broadcast mode) |
BloomFilterDefaultCapacity |
long |
100000 |
Default capacity for BF.RESERVE |
BloomFilterDefaultErrorRate |
double |
0.01 |
Default false-positive rate |
UseUnlinkForBulkDelete |
bool |
true |
Use UNLINK instead of DEL for bulk eviction |
Usage
Standard cache (same API as ICacheService elsewhere)
public class CatalogService(ICacheService cache, IProductRepository repository)
{
public Task<Product?> GetProductAsync(Guid id) =>
cache.GetOrSetAsync(
key: $"product:{id}",
factory: () => repository.GetByIdAsync(id),
expiry: TimeSpan.FromMinutes(30));
}
With UseNativeJson = true (default) the value is stored as a native JSON document, so any other client (or another IRedisJsonCacheService consumer) can read or patch sub-paths of the same key without re-serialising the whole entry.
Partial updates via JSONPath
public class UserProfileService(IRedisJsonCacheService json)
{
public Task PatchEmailAsync(Guid userId, string newEmail) =>
json.MergeAsync(
key: $"user:{userId}",
patch: new { email = newEmail }); // only the "email" field is rewritten server-side
public Task<int> IncrementLoginCountAsync(Guid userId) =>
json.IncrementAsync($"user:{userId}", "$.loginCount", 1)
.ContinueWith(t => (int)t.Result);
public Task<string?> GetEmailAsync(Guid userId) =>
json.GetAsync<string>($"user:{userId}", "$.email");
}
Hash field TTL -- per-tenant or per-user sets
// Store every active session for a user under a single hash, each with its own TTL.
await hashCache.SetFieldAsync(
hashKey: $"sessions:{userId}",
field: sessionId,
value: sessionData,
expiry: TimeSpan.FromMinutes(15));
await hashCache.ExpireFieldAsync($"sessions:{userId}", sessionId, TimeSpan.FromMinutes(15)); // slide
var ttl = await hashCache.GetFieldTtlAsync($"sessions:{userId}", sessionId);
This keeps related entries grouped under one Redis key (so they can be enumerated cheaply) while still expiring them independently -- something plain HSET could not do before Redis 7.4 / 8.
Bloom filter -- cache stampede prevention
public class ProductLookup(IBloomFilterService bloom, IProductRepository repo, ICacheService cache)
{
private const string KnownProductsFilter = "known-products";
public async Task EnsureFilterAsync() =>
await bloom.ReserveAsync(KnownProductsFilter, capacity: 5_000_000, errorRate: 0.001);
public async Task<Product?> GetAsync(string sku)
{
// Skip the DB lookup for SKUs we have never seen before.
if (!await bloom.ExistsAsync(KnownProductsFilter, sku))
return null;
return await cache.GetOrSetAsync($"product:{sku}", () => repo.GetBySkuAsync(sku));
}
public async Task RegisterAsync(Product product)
{
await bloom.AddAsync(KnownProductsFilter, product.Sku);
}
}
Top-K -- identify hot keys for promotion to L1
await topK.ReserveAsync("hot-keys", topK: 50);
// In your read path:
await topK.AddAsync("hot-keys", new[] { cacheKey });
// Periodically, in a Worker:
var hotKeys = await topK.ListAsync("hot-keys");
foreach (var key in hotKeys)
inMemoryCache.Set(key, await cache.GetAsync<object>(key));
Pairs neatly with RepletoryLib.Caching.Hybrid for telemetry-driven L1 promotion.
Vector cache -- semantic search
public class SemanticCache(IVectorCacheService vectors, IEmbeddingService embeddings)
{
public async Task IndexAsync(string id, string content)
{
var embedding = await embeddings.EmbedAsync(content);
await vectors.AddAsync("docs", id, embedding.AsMemory());
}
public async Task<IReadOnlyList<VectorSearchResult>> FindSimilarAsync(string query, int k = 5)
{
var queryEmbedding = await embeddings.EmbedAsync(query);
return await vectors.SearchAsync("docs", queryEmbedding.AsMemory(), count: k);
}
}
Plugs into RepletoryLib.Ai.SemanticSearch as a Redis-resident replacement for the in-memory similarity index.
Distributed locks
Identical API to RepletoryLib.Caching.Redis -- use IDistributedLockService exactly the same way.
How it integrates with the rest of RepletoryLib
| Package | Relationship |
|---|---|
Caching.Abstractions |
Implements ICacheService, IDistributedLockService |
Caching.Redis |
Sibling provider for Redis 7 |
Caching.InMemory |
Alternative for single-instance apps |
Caching.Hybrid |
Can use this provider as L2; combine with ITopKService for hot-key promotion |
Caching.Repository |
CachedRepository<T> benefits from JSON.MERGE for partial entity updates |
Ai.SemanticSearch |
IVectorCacheService is a drop-in Redis-resident vector store |
Auth.Jwt |
Token blacklist stays in Redis; no caller-side changes |
HealthChecks |
Same Redis health check works (any Redis 7+ server) |
Server-assisted client tracking
When EnableClientTracking = true (requires UseResp3 = true), the package issues CLIENT TRACKING ON BCAST NOLOOP after connecting. Redis will then push invalidation notifications to this client for keys matching the configured ClientTrackingPrefixes, allowing a downstream L1 cache to evict stale entries with no pub/sub overhead.
Wire the push handler in your own L1 cache, e.g.:
multiplexer.GetSubscriber().Subscribe(RedisChannel.Literal("__redis__:invalidate"), (_, message) =>
{
foreach (var key in message.ToString().Split(','))
memoryCache.Remove(key);
});
Testing
For unit tests, use MockCacheService from RepletoryLib.Testing. For integration tests against a real Redis 8, point your testcontainer at the redis:8-alpine image and re-use the RedisFixture from RepletoryLib.Testing.Fixtures.
Troubleshooting
| Issue | Solution |
|---|---|
BF.RESERVE / TOPK.RESERVE returns "ERR unknown command" |
Ensure the server is Redis 8 (redis:8-alpine), not 7 -- the probabilistic types are bundled only in 8. |
VADD / VSIM returns "ERR unknown command" |
Vector sets require Redis 8.0+. Earlier 7.x images do not ship them. |
HEXPIRE returns "ERR unknown command" |
Available from Redis 7.4 onwards; default in 8. |
CLIENT TRACKING silently does nothing |
Confirm UseResp3 = true. RESP2 cannot deliver server push frames. |
JSON.SET fails on cluster |
Ensure all shards run Redis 8. Mixed-version clusters should keep using RepletoryLib.Caching.Redis. |
License
This project is licensed under the MIT License.
Copyright (c) 2024-2026 Repletory.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.Caching.StackExchangeRedis (>= 10.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.0)
- NRedisStack (>= 0.13.1)
- RedLock.net (>= 2.3.2)
- RepletoryLib.Caching.Abstractions (>= 1.0.0)
- StackExchange.Redis (>= 2.8.22)
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 |
|---|---|---|
| 1.0.0 | 120 | 5/11/2026 |