DEV Community

magna-nz
magna-nz

Posted on AI-assisted

CLIENT TRACKING landed in Redis 6 four years ago. StackExchange.Redis still doesn't support it.

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
Enter fullscreen mode Exit fullscreen mode

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" });
Enter fullscreen mode Exit fullscreen mode

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:");
});
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)