The gap
Redis 6 shipped CLIENT TRACKING in 2020: opt a connection in, and the server pushes an invalidation the moment a key you've read changes anywhere. It's the building block for a correct client-side
cache — no TTL guessing, no serving stale data because nobody told you it changed.
StackExchange.Redis never implemented it. The issue has been open since 2020([#1461(https://github.com/StackExchange/StackExchange.Redis/issues/1461)), and the 3.x rewrite still ships without it.
Why not just fork it
Forking means tracking every upstream fix and release forever. Instead, RedisNearCache is a package that sits next to StackExchange.Redis: your existing multiplexer keeps doing exactly what it does today, and a second connection — owned and managed by RedisNearCache — handles the tracking and invalidation side-channel.
services.AddRedisNearCache("localhost:6379");
var cache = provider.GetRequiredService<IRedisNearCache>();
await cache.Ready;
var user = await cache.GetAsync<User>("user:42"); // miss: GET + tracked + stored in L1
var again = await cache.GetAsync<User>("user:42"); // hit: served from memory, no round trip
A write to user:42 from any client — not just this process — evicts the local copy within milliseconds:
await cache.SetAsync("user:42", user with { Name = "Ada" });
Two invalidation paths, one API
Redis reached directly (self-hosted, ElastiCache node-based, Azure Cache for Redis) supports REDIRECT-based tracking, which RedisNearCache uses by default. Redis Enterprise-based services
(Azure Managed Redis, Redis Cloud, Redis Software) sit behind a proxy that needs Broadcast mode instead — a separate RESP3 connection per master, subscribed to invalidations for a key prefix you register:
services.AddRedisNearCache("my-cache.region.redis.azure.net:10000,ssl=true,password=<key>", o =>
{
o.TrackingMode = TrackingMode.Broadcast;
o.KeyPrefixes.Add("user:");
});
Everything after registration — GetAsync, SetAsync, statistics — is identical in both modes. It also drops in behind HybridCache and IDistributedCache if that's what your code already targets.
Does it actually help?
Benchmarked against a plain IMemoryCache with a TTL, HybridCache with a Redis L2, and FusionCache with a backplane — 20 app instances, 160 readers, 2,000 writes/s arriving from a separate service:
| Reads/s | Reads served stale | Stalest read | |
|---|---|---|---|
| RedisNearCache | 2.20M | 0.33% | 105ms |
| IMemoryCache + 10s TTL | 14.51M | 83.1% | 10.0s |
| HybridCache + Redis L2 | 25.99M | 84.8% | 10.0s |
| FusionCache + backplane | 6.42M | 85.0% | 10.0s |
The TTL-based options read faster in raw throughput, but they're serving stale data over 80% of the time in this workload — some reads up to the full TTL window old. RedisNearCache trades some of that
throughput for actually knowing when a value changed. Full methodology, cluster/TLS/Valkey/chaos runs, and per-call cost breakdowns are in the benchmark README.
Try it
dotnet add package RedisNearCache
- GitHub: https://github.com/magna-nz/redis-near-cache
- Docs: https://magna-nz.github.io/redis-near-cache/
- NuGet: https://www.nuget.org/packages/RedisNearCache
Issues, especially from anyone running Azure Managed Redis, Redis Cloud, or Redis Software who can put Broadcast mode through its paces, are very welcome.
Top comments (0)